1
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* ***** BEGIN LICENSE BLOCK *****
4
* Version: MPL 1.1/GPL 2.0
6
* The contents of this file are subject to the Mozilla Public License Version
7
* 1.1 (the "License"); you may not use this file except in compliance with
8
* the License. You may obtain a copy of the License at
9
* http://www.mozilla.org/MPL/
11
* Software distributed under the License is distributed on an "AS IS" basis,
12
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13
* for the specific language governing rights and limitations under the
16
* The Original Code is Rhino code, released
19
* The Initial Developer of the Original Code is
20
* Netscape Communications Corporation.
21
* Portions created by the Initial Developer are Copyright (C) 1997-1999
22
* the Initial Developer. All Rights Reserved.
31
* Alternatively, the contents of this file may be used under the terms of
32
* the GNU General Public License Version 2 or later (the "GPL"), in which
33
* case the provisions of the GPL are applicable instead of those above. If
34
* you wish to allow use of your version of this file only under the terms of
35
* the GPL and not to allow others to use your version of this file under the
36
* MPL, indicate your decision by deleting the provisions above and replacing
37
* them with the notice and other provisions required by the GPL. If you do
38
* not delete the provisions above, a recipient may use your version of this
39
* file under either the MPL or the GPL.
41
* ***** END LICENSE BLOCK ***** */
45
package org.mozilla.javascript;
47
import java.lang.reflect.*;
48
import java.util.Hashtable;
50
import org.mozilla.javascript.debug.DebuggableObject;
53
* This is the default implementation of the Scriptable interface. This
54
* class provides convenient default behavior that makes it easier to
55
* define host objects.
57
* Various properties and methods of JavaScript objects can be conveniently
58
* defined using methods of ScriptableObject.
60
* Classes extending ScriptableObject must define the getClassName method.
62
* @see org.mozilla.javascript.Scriptable
66
public abstract class ScriptableObject implements Scriptable, Serializable,
72
* The empty property attribute.
74
* Used by getAttributes() and setAttributes().
76
* @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
77
* @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
79
public static final int EMPTY = 0x00;
82
* Property attribute indicating assignment to this property is ignored.
84
* @see org.mozilla.javascript.ScriptableObject
85
* #put(String, Scriptable, Object)
86
* @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
87
* @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
89
public static final int READONLY = 0x01;
92
* Property attribute indicating property is not enumerated.
94
* Only enumerated properties will be returned by getIds().
96
* @see org.mozilla.javascript.ScriptableObject#getIds()
97
* @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
98
* @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
100
public static final int DONTENUM = 0x02;
103
* Property attribute indicating property cannot be deleted.
105
* @see org.mozilla.javascript.ScriptableObject#delete(String)
106
* @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
107
* @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
109
public static final int PERMANENT = 0x04;
112
* Property attribute indicating that this is a const property that has not
113
* been assigned yet. The first 'const' assignment to the property will
116
public static final int UNINITIALIZED_CONST = 0x08;
118
public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST;
120
* The prototype of this object.
122
private Scriptable prototypeObject;
125
* The parent scope of this object.
127
private Scriptable parentScopeObject;
129
private static final Slot REMOVED = new Slot(null, 0, READONLY);
132
REMOVED.wasDeleted = 1;
135
private transient Slot[] slots;
136
// If count >= 0, it gives number of keys or if count < 0,
137
// it indicates sealed object where ~count gives number of keys
140
// cache; may be removed for smaller memory footprint
141
private transient Slot lastAccess = REMOVED;
143
// associated values are not serialized
144
private transient volatile Hashtable associatedValues;
146
private static final int SLOT_QUERY = 1;
147
private static final int SLOT_MODIFY = 2;
148
private static final int SLOT_REMOVE = 3;
149
private static final int SLOT_MODIFY_GETTER_SETTER = 4;
150
private static final int SLOT_MODIFY_CONST = 5;
152
private static class Slot implements Serializable
154
static final long serialVersionUID = -3539051633409902634L;
156
String name; // This can change due to caching
158
private volatile short attributes;
159
transient volatile byte wasDeleted;
160
volatile Object value;
161
transient volatile Slot next;
163
Slot(String name, int indexOrHash, int attributes)
166
this.indexOrHash = indexOrHash;
167
this.attributes = (short)attributes;
170
private void readObject(ObjectInputStream in)
171
throws IOException, ClassNotFoundException
173
in.defaultReadObject();
175
indexOrHash = name.hashCode();
179
final int getAttributes()
184
final synchronized void setAttributes(int value)
186
checkValidAttributes(value);
187
attributes = (short)value;
190
final void checkNotReadonly()
192
if ((attributes & READONLY) != 0) {
193
String str = (name != null ? name
194
: Integer.toString(indexOrHash));
195
throw Context.reportRuntimeError1("msg.modify.readonly", str);
201
private static final class GetterSlot extends Slot
203
static final long serialVersionUID = -4900574849788797588L;
208
GetterSlot(String name, int indexOrHash, int attributes)
210
super(name, indexOrHash, attributes);
214
static void checkValidAttributes(int attributes)
216
final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST;
217
if ((attributes & ~mask) != 0) {
218
throw new IllegalArgumentException(String.valueOf(attributes));
222
public ScriptableObject()
226
public ScriptableObject(Scriptable scope, Scriptable prototype)
229
throw new IllegalArgumentException();
231
parentScopeObject = scope;
232
prototypeObject = prototype;
236
* Return the name of the class.
238
* This is typically the same name as the constructor.
239
* Classes extending ScriptableObject must implement this abstract
242
public abstract String getClassName();
245
* Returns true if the named property is defined.
247
* @param name the name of the property
248
* @param start the object in which the lookup began
249
* @return true if and only if the property was found in the object
251
public boolean has(String name, Scriptable start)
253
return null != getSlot(name, 0, SLOT_QUERY);
257
* Returns true if the property index is defined.
259
* @param index the numeric index for the property
260
* @param start the object in which the lookup began
261
* @return true if and only if the property was found in the object
263
public boolean has(int index, Scriptable start)
265
return null != getSlot(null, index, SLOT_QUERY);
269
* Returns the value of the named property or NOT_FOUND.
271
* If the property was created using defineProperty, the
272
* appropriate getter method is called.
274
* @param name the name of the property
275
* @param start the object in which the lookup began
276
* @return the value of the property (may be null), or NOT_FOUND
278
public Object get(String name, Scriptable start)
280
return getImpl(name, 0, start);
284
* Returns the value of the indexed property or NOT_FOUND.
286
* @param index the numeric index for the property
287
* @param start the object in which the lookup began
288
* @return the value of the property (may be null), or NOT_FOUND
290
public Object get(int index, Scriptable start)
292
return getImpl(null, index, start);
296
* Sets the value of the named property, creating it if need be.
298
* If the property was created using defineProperty, the
299
* appropriate setter method is called. <p>
301
* If the property's attributes include READONLY, no action is
303
* This method will actually set the property in the start
306
* @param name the name of the property
307
* @param start the object whose property is being set
308
* @param value value to set the property to
310
public void put(String name, Scriptable start, Object value)
312
if (putImpl(name, 0, start, value, EMPTY))
315
if (start == this) throw Kit.codeBug();
316
start.put(name, start, value);
320
* Sets the value of the indexed property, creating it if need be.
322
* @param index the numeric index for the property
323
* @param start the object whose property is being set
324
* @param value value to set the property to
326
public void put(int index, Scriptable start, Object value)
328
if (putImpl(null, index, start, value, EMPTY))
331
if (start == this) throw Kit.codeBug();
332
start.put(index, start, value);
336
* Removes a named property from the object.
338
* If the property is not found, or it has the PERMANENT attribute,
339
* no action is taken.
341
* @param name the name of the property
343
public void delete(String name)
345
checkNotSealed(name, 0);
346
accessSlot(name, 0, SLOT_REMOVE);
350
* Removes the indexed property from the object.
352
* If the property is not found, or it has the PERMANENT attribute,
353
* no action is taken.
355
* @param index the numeric index for the property
357
public void delete(int index)
359
checkNotSealed(null, index);
360
accessSlot(null, index, SLOT_REMOVE);
364
* Sets the value of the named const property, creating it if need be.
366
* If the property was created using defineProperty, the
367
* appropriate setter method is called. <p>
369
* If the property's attributes include READONLY, no action is
371
* This method will actually set the property in the start
374
* @param name the name of the property
375
* @param start the object whose property is being set
376
* @param value value to set the property to
378
public void putConst(String name, Scriptable start, Object value)
380
if (putImpl(name, 0, start, value, READONLY))
383
if (start == this) throw Kit.codeBug();
384
if (start instanceof ConstProperties)
385
((ConstProperties)start).putConst(name, start, value);
387
start.put(name, start, value);
390
public void defineConst(String name, Scriptable start)
392
if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST))
395
if (start == this) throw Kit.codeBug();
396
if (start instanceof ConstProperties)
397
((ConstProperties)start).defineConst(name, start);
400
* Returns true if the named property is defined as a const on this object.
402
* @return true if the named property is defined as a const, false
405
public boolean isConst(String name)
407
Slot slot = getSlot(name, 0, SLOT_QUERY);
411
return (slot.getAttributes() & (PERMANENT|READONLY)) ==
412
(PERMANENT|READONLY);
416
* @deprecated Use {@link #getAttributes(String name)}. The engine always
417
* ignored the start argument.
419
public final int getAttributes(String name, Scriptable start)
421
return getAttributes(name);
425
* @deprecated Use {@link #getAttributes(int index)}. The engine always
426
* ignored the start argument.
428
public final int getAttributes(int index, Scriptable start)
430
return getAttributes(index);
434
* @deprecated Use {@link #setAttributes(String name, int attributes)}.
435
* The engine always ignored the start argument.
437
public final void setAttributes(String name, Scriptable start,
440
setAttributes(name, attributes);
444
* @deprecated Use {@link #setAttributes(int index, int attributes)}.
445
* The engine always ignored the start argument.
447
public void setAttributes(int index, Scriptable start,
450
setAttributes(index, attributes);
454
* Get the attributes of a named property.
456
* The property is specified by <code>name</code>
457
* as defined for <code>has</code>.<p>
459
* @param name the identifier for the property
460
* @return the bitset of attributes
461
* @exception EvaluatorException if the named property is not found
462
* @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
463
* @see org.mozilla.javascript.ScriptableObject#READONLY
464
* @see org.mozilla.javascript.ScriptableObject#DONTENUM
465
* @see org.mozilla.javascript.ScriptableObject#PERMANENT
466
* @see org.mozilla.javascript.ScriptableObject#EMPTY
468
public int getAttributes(String name)
470
return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes();
474
* Get the attributes of an indexed property.
476
* @param index the numeric index for the property
477
* @exception EvaluatorException if the named property is not found
479
* @return the bitset of attributes
480
* @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
481
* @see org.mozilla.javascript.ScriptableObject#READONLY
482
* @see org.mozilla.javascript.ScriptableObject#DONTENUM
483
* @see org.mozilla.javascript.ScriptableObject#PERMANENT
484
* @see org.mozilla.javascript.ScriptableObject#EMPTY
486
public int getAttributes(int index)
488
return findAttributeSlot(null, index, SLOT_QUERY).getAttributes();
492
* Set the attributes of a named property.
494
* The property is specified by <code>name</code>
495
* as defined for <code>has</code>.<p>
497
* The possible attributes are READONLY, DONTENUM,
498
* and PERMANENT. Combinations of attributes
499
* are expressed by the bitwise OR of attributes.
500
* EMPTY is the state of no attributes set. Any unused
501
* bits are reserved for future use.
503
* @param name the name of the property
504
* @param attributes the bitset of attributes
505
* @exception EvaluatorException if the named property is not found
506
* @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
507
* @see org.mozilla.javascript.ScriptableObject#READONLY
508
* @see org.mozilla.javascript.ScriptableObject#DONTENUM
509
* @see org.mozilla.javascript.ScriptableObject#PERMANENT
510
* @see org.mozilla.javascript.ScriptableObject#EMPTY
512
public void setAttributes(String name, int attributes)
514
checkNotSealed(name, 0);
515
findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes);
519
* Set the attributes of an indexed property.
521
* @param index the numeric index for the property
522
* @param attributes the bitset of attributes
523
* @exception EvaluatorException if the named property is not found
524
* @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
525
* @see org.mozilla.javascript.ScriptableObject#READONLY
526
* @see org.mozilla.javascript.ScriptableObject#DONTENUM
527
* @see org.mozilla.javascript.ScriptableObject#PERMANENT
528
* @see org.mozilla.javascript.ScriptableObject#EMPTY
530
public void setAttributes(int index, int attributes)
532
checkNotSealed(null, index);
533
findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes);
539
public void setGetterOrSetter(String name, int index,
540
Callable getterOrSeter, boolean isSetter)
542
if (name != null && index != 0)
543
throw new IllegalArgumentException(name);
545
checkNotSealed(name, index);
546
GetterSlot gslot = (GetterSlot)getSlot(name, index,
547
SLOT_MODIFY_GETTER_SETTER);
548
gslot.checkNotReadonly();
550
gslot.setter = getterOrSeter;
552
gslot.getter = getterOrSeter;
554
gslot.value = Undefined.instance;
558
* Get the getter or setter for a given property. Used by __lookupGetter__
559
* and __lookupSetter__.
561
* @param name Name of the object. If nonnull, index must be 0.
562
* @param index Index of the object. If nonzero, name must be null.
563
* @param isSetter If true, return the setter, otherwise return the getter.
564
* @exception IllegalArgumentException if both name and index are nonnull
565
* and nonzero respectively.
566
* @return Null if the property does not exist. Otherwise returns either
567
* the getter or the setter for the property, depending on
568
* the value of isSetter (may be undefined if unset).
570
public Object getGetterOrSetter(String name, int index, boolean isSetter)
572
if (name != null && index != 0)
573
throw new IllegalArgumentException(name);
574
Slot slot = getSlot(name, index, SLOT_QUERY);
577
if (slot instanceof GetterSlot) {
578
GetterSlot gslot = (GetterSlot)slot;
579
Object result = isSetter ? gslot.setter : gslot.getter;
580
return result != null ? result : Undefined.instance;
582
return Undefined.instance;
586
* Returns whether a property is a getter or a setter
587
* @param name property name
588
* @param index property index
589
* @param setter true to check for a setter, false for a getter
590
* @return whether the property is a getter or a setter
592
protected boolean isGetterOrSetter(String name, int index, boolean setter) {
593
Slot slot = getSlot(name, index, SLOT_QUERY);
594
if (slot instanceof GetterSlot) {
595
if (setter && ((GetterSlot)slot).setter != null) return true;
596
if (!setter && ((GetterSlot)slot).getter != null) return true;
601
void addLazilyInitializedValue(String name, int index,
602
LazilyLoadedCtor init, int attributes)
604
if (name != null && index != 0)
605
throw new IllegalArgumentException(name);
606
checkNotSealed(name, index);
607
GetterSlot gslot = (GetterSlot)getSlot(name, index,
608
SLOT_MODIFY_GETTER_SETTER);
609
gslot.setAttributes(attributes);
616
* Returns the prototype of the object.
618
public Scriptable getPrototype()
620
return prototypeObject;
624
* Sets the prototype of the object.
626
public void setPrototype(Scriptable m)
632
* Returns the parent (enclosing) scope of the object.
634
public Scriptable getParentScope()
636
return parentScopeObject;
640
* Sets the parent (enclosing) scope of the object.
642
public void setParentScope(Scriptable m)
644
parentScopeObject = m;
648
* Returns an array of ids for the properties of the object.
650
* <p>Any properties with the attribute DONTENUM are not listed. <p>
652
* @return an array of java.lang.Objects with an entry for every
653
* listed property. Properties accessed via an integer index will
654
* have a corresponding
655
* Integer entry in the returned array. Properties accessed by
656
* a String will have a String entry in the returned array.
658
public Object[] getIds() {
659
return getIds(false);
663
* Returns an array of ids for the properties of the object.
665
* <p>All properties, even those with attribute DONTENUM, are listed. <p>
667
* @return an array of java.lang.Objects with an entry for every
668
* listed property. Properties accessed via an integer index will
669
* have a corresponding
670
* Integer entry in the returned array. Properties accessed by
671
* a String will have a String entry in the returned array.
673
public Object[] getAllIds() {
678
* Implements the [[DefaultValue]] internal method.
680
* <p>Note that the toPrimitive conversion is a no-op for
681
* every type other than Object, for which [[DefaultValue]]
682
* is called. See ECMA 9.1.<p>
684
* A <code>hint</code> of null means "no hint".
686
* @param typeHint the type hint
687
* @return the default value for the object
691
public Object getDefaultValue(Class typeHint)
693
return getDefaultValue(this, typeHint);
696
public static Object getDefaultValue(Scriptable object, Class typeHint)
699
for (int i=0; i < 2; i++) {
701
if (typeHint == ScriptRuntime.StringClass) {
702
tryToString = (i == 0);
704
tryToString = (i == 1);
710
methodName = "toString";
711
args = ScriptRuntime.emptyArgs;
713
methodName = "valueOf";
714
args = new Object[1];
716
if (typeHint == null) {
718
} else if (typeHint == ScriptRuntime.StringClass) {
720
} else if (typeHint == ScriptRuntime.ScriptableClass) {
722
} else if (typeHint == ScriptRuntime.FunctionClass) {
724
} else if (typeHint == ScriptRuntime.BooleanClass
725
|| typeHint == Boolean.TYPE)
728
} else if (typeHint == ScriptRuntime.NumberClass ||
729
typeHint == ScriptRuntime.ByteClass ||
730
typeHint == Byte.TYPE ||
731
typeHint == ScriptRuntime.ShortClass ||
732
typeHint == Short.TYPE ||
733
typeHint == ScriptRuntime.IntegerClass ||
734
typeHint == Integer.TYPE ||
735
typeHint == ScriptRuntime.FloatClass ||
736
typeHint == Float.TYPE ||
737
typeHint == ScriptRuntime.DoubleClass ||
738
typeHint == Double.TYPE)
742
throw Context.reportRuntimeError1(
743
"msg.invalid.type", typeHint.toString());
747
Object v = getProperty(object, methodName);
748
if (!(v instanceof Function))
750
Function fun = (Function) v;
752
cx = Context.getContext();
753
v = fun.call(cx, fun.getParentScope(), object, args);
755
if (!(v instanceof Scriptable)) {
758
if (typeHint == ScriptRuntime.ScriptableClass
759
|| typeHint == ScriptRuntime.FunctionClass)
763
if (tryToString && v instanceof Wrapper) {
764
// Let a wrapped java.lang.String pass for a primitive
766
Object u = ((Wrapper)v).unwrap();
767
if (u instanceof String)
772
// fall through to error
773
String arg = (typeHint == null) ? "undefined" : typeHint.getName();
774
throw ScriptRuntime.typeError1("msg.default.value", arg);
778
* Implements the instanceof operator.
780
* <p>This operator has been proposed to ECMA.
782
* @param instance The value that appeared on the LHS of the instanceof
784
* @return true if "this" appears in value's prototype chain
787
public boolean hasInstance(Scriptable instance) {
788
// Default for JS objects (other than Function) is to do prototype
789
// chasing. This will be overridden in NativeFunction and non-JS
792
return ScriptRuntime.jsDelegatesTo(instance, this);
796
* Emulate the SpiderMonkey (and Firefox) feature of allowing
797
* custom objects to avoid detection by normal "object detection"
798
* code patterns. This is used to implement document.all.
799
* See https://bugzilla.mozilla.org/show_bug.cgi?id=412247.
800
* This is an analog to JOF_DETECTING from SpiderMonkey; see
801
* https://bugzilla.mozilla.org/show_bug.cgi?id=248549.
802
* Other than this special case, embeddings should return false.
803
* @return true if this object should avoid object detection
806
public boolean avoidObjectDetection() {
811
* Custom <tt>==</tt> operator.
812
* Must return {@link Scriptable#NOT_FOUND} if this object does not
813
* have custom equality operator for the given value,
814
* <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>,
815
* <tt>Boolean.FALSE</tt> if this object is not equivalent to
818
* The default implementation returns Boolean.TRUE
819
* if <tt>this == value</tt> or {@link Scriptable#NOT_FOUND} otherwise.
820
* It indicates that by default custom equality is available only if
821
* <tt>value</tt> is <tt>this</tt> in which case true is returned.
823
protected Object equivalentValues(Object value)
825
return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND;
829
* Defines JavaScript objects from a Java class that implements Scriptable.
831
* If the given class has a method
833
* static void init(Context cx, Scriptable scope, boolean sealed);</pre>
835
* or its compatibility form
837
* static void init(Scriptable scope);</pre>
839
* then it is invoked and no further initialization is done.<p>
841
* However, if no such a method is found, then the class's constructors and
842
* methods are used to initialize a class in the following manner.<p>
844
* First, the zero-parameter constructor of the class is called to
845
* create the prototype. If no such constructor exists,
846
* a {@link EvaluatorException} is thrown. <p>
848
* Next, all methods are scanned for special prefixes that indicate that they
849
* have special meaning for defining JavaScript objects.
850
* These special prefixes are
852
* <li><code>jsFunction_</code> for a JavaScript function
853
* <li><code>jsStaticFunction_</code> for a JavaScript function that
854
* is a property of the constructor
855
* <li><code>jsGet_</code> for a getter of a JavaScript property
856
* <li><code>jsSet_</code> for a setter of a JavaScript property
857
* <li><code>jsConstructor</code> for a JavaScript function that
861
* If the method's name begins with "jsFunction_", a JavaScript function
862
* is created with a name formed from the rest of the Java method name
863
* following "jsFunction_". So a Java method named "jsFunction_foo" will
864
* define a JavaScript method "foo". Calling this JavaScript function
865
* will cause the Java method to be called. The parameters of the method
866
* must be of number and types as defined by the FunctionObject class.
867
* The JavaScript function is then added as a property
868
* of the prototype. <p>
870
* If the method's name begins with "jsStaticFunction_", it is handled
871
* similarly except that the resulting JavaScript function is added as a
872
* property of the constructor object. The Java method must be static.
874
* If the method's name begins with "jsGet_" or "jsSet_", the method is
875
* considered to define a property. Accesses to the defined property
876
* will result in calls to these getter and setter methods. If no
877
* setter is defined, the property is defined as READONLY.<p>
879
* If the method's name is "jsConstructor", the method is
880
* considered to define the body of the constructor. Only one
881
* method of this name may be defined.
882
* If no method is found that can serve as constructor, a Java
883
* constructor will be selected to serve as the JavaScript
884
* constructor in the following manner. If the class has only one
885
* Java constructor, that constructor is used to define
886
* the JavaScript constructor. If the the class has two constructors,
887
* one must be the zero-argument constructor (otherwise an
888
* {@link EvaluatorException} would have already been thrown
889
* when the prototype was to be created). In this case
890
* the Java constructor with one or more parameters will be used
891
* to define the JavaScript constructor. If the class has three
892
* or more constructors, an {@link EvaluatorException}
895
* Finally, if there is a method
897
* static void finishInit(Scriptable scope, FunctionObject constructor,
898
* Scriptable prototype)</pre>
900
* it will be called to finish any initialization. The <code>scope</code>
901
* argument will be passed, along with the newly created constructor and
902
* the newly created prototype.<p>
904
* @param scope The scope in which to define the constructor.
905
* @param clazz The Java class to use to define the JavaScript objects
907
* @exception IllegalAccessException if access is not available
908
* to a reflected class member
909
* @exception InstantiationException if unable to instantiate
911
* @exception InvocationTargetException if an exception is thrown
912
* during execution of methods of the named class
913
* @see org.mozilla.javascript.Function
914
* @see org.mozilla.javascript.FunctionObject
915
* @see org.mozilla.javascript.ScriptableObject#READONLY
916
* @see org.mozilla.javascript.ScriptableObject
917
* #defineProperty(String, Class, int)
919
public static void defineClass(Scriptable scope, Class clazz)
920
throws IllegalAccessException, InstantiationException,
921
InvocationTargetException
923
defineClass(scope, clazz, false, false);
927
* Defines JavaScript objects from a Java class, optionally
930
* Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
931
* except that sealing is allowed. An object that is sealed cannot have
932
* properties added or removed. Note that sealing is not allowed in
933
* the current ECMA/ISO language specification, but is likely for
936
* @param scope The scope in which to define the constructor.
937
* @param clazz The Java class to use to define the JavaScript objects
938
* and properties. The class must implement Scriptable.
939
* @param sealed Whether or not to create sealed standard objects that
940
* cannot be modified.
941
* @exception IllegalAccessException if access is not available
942
* to a reflected class member
943
* @exception InstantiationException if unable to instantiate
945
* @exception InvocationTargetException if an exception is thrown
946
* during execution of methods of the named class
949
public static void defineClass(Scriptable scope, Class clazz,
951
throws IllegalAccessException, InstantiationException,
952
InvocationTargetException
954
defineClass(scope, clazz, sealed, false);
958
* Defines JavaScript objects from a Java class, optionally
959
* allowing sealing and mapping of Java inheritance to JavaScript
960
* prototype-based inheritance.
962
* Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
963
* except that sealing and inheritance mapping are allowed. An object
964
* that is sealed cannot have properties added or removed. Note that
965
* sealing is not allowed in the current ECMA/ISO language specification,
966
* but is likely for the next version.
968
* @param scope The scope in which to define the constructor.
969
* @param clazz The Java class to use to define the JavaScript objects
970
* and properties. The class must implement Scriptable.
971
* @param sealed Whether or not to create sealed standard objects that
972
* cannot be modified.
973
* @param mapInheritance Whether or not to map Java inheritance to
974
* JavaScript prototype-based inheritance.
975
* @return the class name for the prototype of the specified class
976
* @exception IllegalAccessException if access is not available
977
* to a reflected class member
978
* @exception InstantiationException if unable to instantiate
980
* @exception InvocationTargetException if an exception is thrown
981
* during execution of methods of the named class
984
public static String defineClass(Scriptable scope, Class clazz,
985
boolean sealed, boolean mapInheritance)
986
throws IllegalAccessException, InstantiationException,
987
InvocationTargetException
989
BaseFunction ctor = buildClassCtor(scope, clazz, sealed,
993
String name = ctor.getClassPrototype().getClassName();
994
defineProperty(scope, name, ctor, ScriptableObject.DONTENUM);
998
static BaseFunction buildClassCtor(Scriptable scope, Class clazz,
1000
boolean mapInheritance)
1001
throws IllegalAccessException, InstantiationException,
1002
InvocationTargetException
1004
Method[] methods = FunctionObject.getMethodList(clazz);
1005
for (int i=0; i < methods.length; i++) {
1006
Method method = methods[i];
1007
if (!method.getName().equals("init"))
1009
Class[] parmTypes = method.getParameterTypes();
1010
if (parmTypes.length == 3 &&
1011
parmTypes[0] == ScriptRuntime.ContextClass &&
1012
parmTypes[1] == ScriptRuntime.ScriptableClass &&
1013
parmTypes[2] == Boolean.TYPE &&
1014
Modifier.isStatic(method.getModifiers()))
1016
Object args[] = { Context.getContext(), scope,
1017
sealed ? Boolean.TRUE : Boolean.FALSE };
1018
method.invoke(null, args);
1021
if (parmTypes.length == 1 &&
1022
parmTypes[0] == ScriptRuntime.ScriptableClass &&
1023
Modifier.isStatic(method.getModifiers()))
1025
Object args[] = { scope };
1026
method.invoke(null, args);
1032
// If we got here, there isn't an "init" method with the right
1035
Constructor[] ctors = clazz.getConstructors();
1036
Constructor protoCtor = null;
1037
for (int i=0; i < ctors.length; i++) {
1038
if (ctors[i].getParameterTypes().length == 0) {
1039
protoCtor = ctors[i];
1043
if (protoCtor == null) {
1044
throw Context.reportRuntimeError1(
1045
"msg.zero.arg.ctor", clazz.getName());
1048
Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs);
1049
String className = proto.getClassName();
1051
// Set the prototype's prototype, trying to map Java inheritance to JS
1052
// prototype-based inheritance if requested to do so.
1053
Scriptable superProto = null;
1054
if (mapInheritance) {
1055
Class superClass = clazz.getSuperclass();
1056
if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass)
1057
&& !Modifier.isAbstract(superClass.getModifiers())) {
1058
String name = ScriptableObject.defineClass(scope, superClass, sealed, mapInheritance);
1060
superProto = ScriptableObject.getClassPrototype(scope, name);
1064
if (superProto == null) {
1065
superProto = ScriptableObject.getObjectPrototype(scope);
1067
proto.setPrototype(superProto);
1069
// Find out whether there are any methods that begin with
1070
// "js". If so, then only methods that begin with special
1071
// prefixes will be defined as JavaScript entities.
1072
final String functionPrefix = "jsFunction_";
1073
final String staticFunctionPrefix = "jsStaticFunction_";
1074
final String getterPrefix = "jsGet_";
1075
final String setterPrefix = "jsSet_";
1076
final String ctorName = "jsConstructor";
1078
Member ctorMember = FunctionObject.findSingleMethod(methods, ctorName);
1080
if (ctorMember == null) {
1081
if (ctors.length == 1) {
1082
ctorMember = ctors[0];
1083
} else if (ctors.length == 2) {
1084
if (ctors[0].getParameterTypes().length == 0)
1085
ctorMember = ctors[1];
1086
else if (ctors[1].getParameterTypes().length == 0)
1087
ctorMember = ctors[0];
1089
if (ctorMember == null) {
1090
throw Context.reportRuntimeError1(
1091
"msg.ctor.multiple.parms", clazz.getName());
1095
FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
1096
if (ctor.isVarArgsMethod()) {
1097
throw Context.reportRuntimeError1
1098
("msg.varargs.ctor", ctorMember.getName());
1100
ctor.initAsConstructor(scope, proto);
1102
Method finishInit = null;
1103
for (int i=0; i < methods.length; i++) {
1104
if (methods[i] == ctorMember) {
1107
String name = methods[i].getName();
1108
if (name.equals("finishInit")) {
1109
Class[] parmTypes = methods[i].getParameterTypes();
1110
if (parmTypes.length == 3 &&
1111
parmTypes[0] == ScriptRuntime.ScriptableClass &&
1112
parmTypes[1] == FunctionObject.class &&
1113
parmTypes[2] == ScriptRuntime.ScriptableClass &&
1114
Modifier.isStatic(methods[i].getModifiers()))
1116
finishInit = methods[i];
1120
// ignore any compiler generated methods.
1121
if (name.indexOf('$') != -1)
1123
if (name.equals(ctorName))
1126
String prefix = null;
1127
if (name.startsWith(functionPrefix)) {
1128
prefix = functionPrefix;
1129
} else if (name.startsWith(staticFunctionPrefix)) {
1130
prefix = staticFunctionPrefix;
1131
if (!Modifier.isStatic(methods[i].getModifiers())) {
1132
throw Context.reportRuntimeError(
1133
"jsStaticFunction must be used with static method.");
1135
} else if (name.startsWith(getterPrefix)) {
1136
prefix = getterPrefix;
1137
} else if (name.startsWith(setterPrefix)) {
1138
prefix = setterPrefix;
1142
name = name.substring(prefix.length());
1143
if (prefix == setterPrefix)
1144
continue; // deal with set when we see get
1145
if (prefix == getterPrefix) {
1146
if (!(proto instanceof ScriptableObject)) {
1147
throw Context.reportRuntimeError2(
1148
"msg.extend.scriptable",
1149
proto.getClass().toString(), name);
1151
Method setter = FunctionObject.findSingleMethod(
1153
setterPrefix + name);
1154
int attr = ScriptableObject.PERMANENT |
1155
ScriptableObject.DONTENUM |
1157
: ScriptableObject.READONLY);
1158
((ScriptableObject) proto).defineProperty(name, null,
1164
FunctionObject f = new FunctionObject(name, methods[i], proto);
1165
if (f.isVarArgsConstructor()) {
1166
throw Context.reportRuntimeError1
1167
("msg.varargs.fun", ctorMember.getName());
1169
Scriptable dest = prefix == staticFunctionPrefix
1172
defineProperty(dest, name, f, DONTENUM);
1178
// Call user code to complete initialization if necessary.
1179
if (finishInit != null) {
1180
Object[] finishArgs = { scope, ctor, proto };
1181
finishInit.invoke(null, finishArgs);
1184
// Seal the object if necessary.
1187
if (proto instanceof ScriptableObject) {
1188
((ScriptableObject) proto).sealObject();
1196
* Define a JavaScript property.
1198
* Creates the property with an initial value and sets its attributes.
1200
* @param propertyName the name of the property to define.
1201
* @param value the initial value of the property
1202
* @param attributes the attributes of the JavaScript property
1203
* @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
1205
public void defineProperty(String propertyName, Object value,
1208
checkNotSealed(propertyName, 0);
1209
put(propertyName, this, value);
1210
setAttributes(propertyName, attributes);
1214
* Utility method to add properties to arbitrary Scriptable object.
1215
* If destination is instance of ScriptableObject, calls
1216
* defineProperty there, otherwise calls put in destination
1217
* ignoring attributes
1219
public static void defineProperty(Scriptable destination,
1220
String propertyName, Object value,
1223
if (!(destination instanceof ScriptableObject)) {
1224
destination.put(propertyName, destination, value);
1227
ScriptableObject so = (ScriptableObject)destination;
1228
so.defineProperty(propertyName, value, attributes);
1232
* Utility method to add properties to arbitrary Scriptable object.
1233
* If destination is instance of ScriptableObject, calls
1234
* defineProperty there, otherwise calls put in destination
1235
* ignoring attributes
1237
public static void defineConstProperty(Scriptable destination,
1238
String propertyName)
1240
if (destination instanceof ConstProperties) {
1241
ConstProperties cp = (ConstProperties)destination;
1242
cp.defineConst(propertyName, destination);
1244
defineProperty(destination, propertyName, Undefined.instance, CONST);
1248
* Define a JavaScript property with getter and setter side effects.
1250
* If the setter is not found, the attribute READONLY is added to
1251
* the given attributes. <p>
1253
* The getter must be a method with zero parameters, and the setter, if
1254
* found, must be a method with one parameter.<p>
1256
* @param propertyName the name of the property to define. This name
1257
* also affects the name of the setter and getter
1258
* to search for. If the propertyId is "foo", then
1259
* <code>clazz</code> will be searched for "getFoo"
1260
* and "setFoo" methods.
1261
* @param clazz the Java class to search for the getter and setter
1262
* @param attributes the attributes of the JavaScript property
1263
* @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
1265
public void defineProperty(String propertyName, Class clazz,
1268
int length = propertyName.length();
1269
if (length == 0) throw new IllegalArgumentException();
1270
char[] buf = new char[3 + length];
1271
propertyName.getChars(0, length, buf, 3);
1272
buf[3] = Character.toUpperCase(buf[3]);
1276
String getterName = new String(buf);
1278
String setterName = new String(buf);
1280
Method[] methods = FunctionObject.getMethodList(clazz);
1281
Method getter = FunctionObject.findSingleMethod(methods, getterName);
1282
Method setter = FunctionObject.findSingleMethod(methods, setterName);
1284
attributes |= ScriptableObject.READONLY;
1285
defineProperty(propertyName, null, getter,
1286
setter == null ? null : setter, attributes);
1290
* Define a JavaScript property.
1292
* Use this method only if you wish to define getters and setters for
1293
* a given property in a ScriptableObject. To create a property without
1294
* special getter or setter side effects, use
1295
* <code>defineProperty(String,int)</code>.
1297
* If <code>setter</code> is null, the attribute READONLY is added to
1298
* the given attributes.<p>
1300
* Several forms of getters or setters are allowed. In all cases the
1301
* type of the value parameter can be any one of the following types:
1302
* Object, String, boolean, Scriptable, byte, short, int, long, float,
1303
* or double. The runtime will perform appropriate conversions based
1304
* upon the type of the parameter (see description in FunctionObject).
1305
* The first forms are nonstatic methods of the class referred to
1309
* void setFoo(SomeType value);</pre>
1310
* Next are static methods that may be of any class; the object whose
1311
* property is being accessed is passed in as an extra argument:
1313
* static Object getFoo(Scriptable obj);
1314
* static void setFoo(Scriptable obj, SomeType value);</pre>
1315
* Finally, it is possible to delegate to another object entirely using
1316
* the <code>delegateTo</code> parameter. In this case the methods are
1317
* nonstatic methods of the class delegated to, and the object whose
1318
* property is being accessed is passed in as an extra argument:
1320
* Object getFoo(Scriptable obj);
1321
* void setFoo(Scriptable obj, SomeType value);</pre>
1323
* @param propertyName the name of the property to define.
1324
* @param delegateTo an object to call the getter and setter methods on,
1325
* or null, depending on the form used above.
1326
* @param getter the method to invoke to get the value of the property
1327
* @param setter the method to invoke to set the value of the property
1328
* @param attributes the attributes of the JavaScript property
1330
public void defineProperty(String propertyName, Object delegateTo,
1331
Method getter, Method setter, int attributes)
1333
MemberBox getterBox = null;
1334
if (getter != null) {
1335
getterBox = new MemberBox(getter);
1337
boolean delegatedForm;
1338
if (!Modifier.isStatic(getter.getModifiers())) {
1339
delegatedForm = (delegateTo != null);
1340
getterBox.delegateTo = delegateTo;
1342
delegatedForm = true;
1343
// Ignore delegateTo for static getter but store
1344
// non-null delegateTo indicator.
1345
getterBox.delegateTo = Void.TYPE;
1348
String errorId = null;
1349
Class[] parmTypes = getter.getParameterTypes();
1350
if (parmTypes.length == 0) {
1351
if (delegatedForm) {
1352
errorId = "msg.obj.getter.parms";
1354
} else if (parmTypes.length == 1) {
1355
Object argType = parmTypes[0];
1356
// Allow ScriptableObject for compatibility
1357
if (!(argType == ScriptRuntime.ScriptableClass ||
1358
argType == ScriptRuntime.ScriptableObjectClass))
1360
errorId = "msg.bad.getter.parms";
1361
} else if (!delegatedForm) {
1362
errorId = "msg.bad.getter.parms";
1365
errorId = "msg.bad.getter.parms";
1367
if (errorId != null) {
1368
throw Context.reportRuntimeError1(errorId, getter.toString());
1372
MemberBox setterBox = null;
1373
if (setter != null) {
1374
if (setter.getReturnType() != Void.TYPE)
1375
throw Context.reportRuntimeError1("msg.setter.return",
1378
setterBox = new MemberBox(setter);
1380
boolean delegatedForm;
1381
if (!Modifier.isStatic(setter.getModifiers())) {
1382
delegatedForm = (delegateTo != null);
1383
setterBox.delegateTo = delegateTo;
1385
delegatedForm = true;
1386
// Ignore delegateTo for static setter but store
1387
// non-null delegateTo indicator.
1388
setterBox.delegateTo = Void.TYPE;
1391
String errorId = null;
1392
Class[] parmTypes = setter.getParameterTypes();
1393
if (parmTypes.length == 1) {
1394
if (delegatedForm) {
1395
errorId = "msg.setter2.expected";
1397
} else if (parmTypes.length == 2) {
1398
Object argType = parmTypes[0];
1399
// Allow ScriptableObject for compatibility
1400
if (!(argType == ScriptRuntime.ScriptableClass ||
1401
argType == ScriptRuntime.ScriptableObjectClass))
1403
errorId = "msg.setter2.parms";
1404
} else if (!delegatedForm) {
1405
errorId = "msg.setter1.parms";
1408
errorId = "msg.setter.parms";
1410
if (errorId != null) {
1411
throw Context.reportRuntimeError1(errorId, setter.toString());
1415
GetterSlot gslot = (GetterSlot)getSlot(propertyName, 0,
1416
SLOT_MODIFY_GETTER_SETTER);
1417
gslot.setAttributes(attributes);
1418
gslot.getter = getterBox;
1419
gslot.setter = setterBox;
1423
* Search for names in a class, adding the resulting methods
1426
* <p> Uses reflection to find the methods of the given names. Then
1427
* FunctionObjects are constructed from the methods found, and
1428
* are added to this object as properties with the given names.
1430
* @param names the names of the Methods to add as function properties
1431
* @param clazz the class to search for the Methods
1432
* @param attributes the attributes of the new properties
1433
* @see org.mozilla.javascript.FunctionObject
1435
public void defineFunctionProperties(String[] names, Class clazz,
1438
Method[] methods = FunctionObject.getMethodList(clazz);
1439
for (int i=0; i < names.length; i++) {
1440
String name = names[i];
1441
Method m = FunctionObject.findSingleMethod(methods, name);
1443
throw Context.reportRuntimeError2(
1444
"msg.method.not.found", name, clazz.getName());
1446
FunctionObject f = new FunctionObject(name, m, this);
1447
defineProperty(name, f, attributes);
1452
* Get the Object.prototype property.
1455
public static Scriptable getObjectPrototype(Scriptable scope) {
1456
return getClassPrototype(scope, "Object");
1460
* Get the Function.prototype property.
1463
public static Scriptable getFunctionPrototype(Scriptable scope) {
1464
return getClassPrototype(scope, "Function");
1468
* Get the prototype for the named class.
1470
* For example, <code>getClassPrototype(s, "Date")</code> will first
1471
* walk up the parent chain to find the outermost scope, then will
1472
* search that scope for the Date constructor, and then will
1473
* return Date.prototype. If any of the lookups fail, or
1474
* the prototype is not a JavaScript object, then null will
1477
* @param scope an object in the scope chain
1478
* @param className the name of the constructor
1479
* @return the prototype for the named class, or null if it
1482
public static Scriptable getClassPrototype(Scriptable scope,
1485
scope = getTopLevelScope(scope);
1486
Object ctor = getProperty(scope, className);
1488
if (ctor instanceof BaseFunction) {
1489
proto = ((BaseFunction)ctor).getPrototypeProperty();
1490
} else if (ctor instanceof Scriptable) {
1491
Scriptable ctorObj = (Scriptable)ctor;
1492
proto = ctorObj.get("prototype", ctorObj);
1496
if (proto instanceof Scriptable) {
1497
return (Scriptable)proto;
1503
* Get the global scope.
1505
* <p>Walks the parent scope chain to find an object with a null
1506
* parent scope (the global object).
1508
* @param obj a JavaScript object
1509
* @return the corresponding global scope
1511
public static Scriptable getTopLevelScope(Scriptable obj)
1514
Scriptable parent = obj.getParentScope();
1515
if (parent == null) {
1523
public static Scriptable getVeryTopLevelScope(Scriptable obj) {
1524
return ScriptRuntime.getLibraryScopeOrNull(obj);
1530
* A sealed object may not have properties added or removed. Once
1531
* an object is sealed it may not be unsealed.
1535
public synchronized void sealObject() {
1542
* Return true if this object is sealed.
1544
* It is an error to attempt to add or remove properties to
1547
* @return true if sealed, false otherwise.
1550
public final boolean isSealed() {
1554
private void checkNotSealed(String name, int index)
1559
String str = (name != null) ? name : Integer.toString(index);
1560
throw Context.reportRuntimeError1("msg.modify.sealed", str);
1564
* Gets a named property from an object or any object in its prototype chain.
1566
* Searches the prototype chain for a property named <code>name</code>.
1568
* @param obj a JavaScript object
1569
* @param name a property name
1570
* @return the value of a property with name <code>name</code> found in
1571
* <code>obj</code> or any object in its prototype chain, or
1572
* <code>Scriptable.NOT_FOUND</code> if not found
1575
public static Object getProperty(Scriptable obj, String name)
1577
Scriptable start = obj;
1580
result = obj.get(name, start);
1581
if (result != Scriptable.NOT_FOUND)
1583
obj = obj.getPrototype();
1584
} while (obj != null);
1589
* Gets an indexed property from an object or any object in its prototype chain.
1591
* Searches the prototype chain for a property with integral index
1592
* <code>index</code>. Note that if you wish to look for properties with numerical
1593
* but non-integral indicies, you should use getProperty(Scriptable,String) with
1594
* the string value of the index.
1596
* @param obj a JavaScript object
1597
* @param index an integral index
1598
* @return the value of a property with index <code>index</code> found in
1599
* <code>obj</code> or any object in its prototype chain, or
1600
* <code>Scriptable.NOT_FOUND</code> if not found
1603
public static Object getProperty(Scriptable obj, int index)
1605
Scriptable start = obj;
1608
result = obj.get(index, start);
1609
if (result != Scriptable.NOT_FOUND)
1611
obj = obj.getPrototype();
1612
} while (obj != null);
1617
* Returns whether a named property is defined in an object or any object
1618
* in its prototype chain.
1620
* Searches the prototype chain for a property named <code>name</code>.
1622
* @param obj a JavaScript object
1623
* @param name a property name
1624
* @return the true if property was found
1627
public static boolean hasProperty(Scriptable obj, String name)
1629
return null != getBase(obj, name);
1633
* If hasProperty(obj, name) would return true, then if the property that
1634
* was found is compatible with the new property, this method just returns.
1635
* If the property is not compatible, then an exception is thrown.
1637
* A property redefinition is incompatible if the first definition was a
1638
* const declaration or if this one is. They are compatible only if neither
1641
public static void redefineProperty(Scriptable obj, String name,
1644
Scriptable base = getBase(obj, name);
1647
if (base instanceof ConstProperties) {
1648
ConstProperties cp = (ConstProperties)base;
1650
if (cp.isConst(name))
1651
throw Context.reportRuntimeError1("msg.const.redecl", name);
1654
throw Context.reportRuntimeError1("msg.var.redecl", name);
1657
* Returns whether an indexed property is defined in an object or any object
1658
* in its prototype chain.
1660
* Searches the prototype chain for a property with index <code>index</code>.
1662
* @param obj a JavaScript object
1663
* @param index a property index
1664
* @return the true if property was found
1667
public static boolean hasProperty(Scriptable obj, int index)
1669
return null != getBase(obj, index);
1673
* Puts a named property in an object or in an object in its prototype chain.
1675
* Searches for the named property in the prototype chain. If it is found,
1676
* the value of the property in <code>obj</code> is changed through a call
1677
* to {@link Scriptable#put(String, Scriptable, Object)} on the
1678
* prototype passing <code>obj</code> as the <code>start</code> argument.
1679
* This allows the prototype to veto the property setting in case the
1680
* prototype defines the property with [[ReadOnly]] attribute. If the
1681
* property is not found, it is added in <code>obj</code>.
1682
* @param obj a JavaScript object
1683
* @param name a property name
1684
* @param value any JavaScript value accepted by Scriptable.put
1687
public static void putProperty(Scriptable obj, String name, Object value)
1689
Scriptable base = getBase(obj, name);
1692
base.put(name, obj, value);
1696
* Puts a named property in an object or in an object in its prototype chain.
1698
* Searches for the named property in the prototype chain. If it is found,
1699
* the value of the property in <code>obj</code> is changed through a call
1700
* to {@link Scriptable#put(String, Scriptable, Object)} on the
1701
* prototype passing <code>obj</code> as the <code>start</code> argument.
1702
* This allows the prototype to veto the property setting in case the
1703
* prototype defines the property with [[ReadOnly]] attribute. If the
1704
* property is not found, it is added in <code>obj</code>.
1705
* @param obj a JavaScript object
1706
* @param name a property name
1707
* @param value any JavaScript value accepted by Scriptable.put
1710
public static void putConstProperty(Scriptable obj, String name, Object value)
1712
Scriptable base = getBase(obj, name);
1715
if (base instanceof ConstProperties)
1716
((ConstProperties)base).putConst(name, obj, value);
1720
* Puts an indexed property in an object or in an object in its prototype chain.
1722
* Searches for the indexed property in the prototype chain. If it is found,
1723
* the value of the property in <code>obj</code> is changed through a call
1724
* to {@link Scriptable#put(int, Scriptable, Object)} on the prototype
1725
* passing <code>obj</code> as the <code>start</code> argument. This allows
1726
* the prototype to veto the property setting in case the prototype defines
1727
* the property with [[ReadOnly]] attribute. If the property is not found,
1728
* it is added in <code>obj</code>.
1729
* @param obj a JavaScript object
1730
* @param index a property index
1731
* @param value any JavaScript value accepted by Scriptable.put
1734
public static void putProperty(Scriptable obj, int index, Object value)
1736
Scriptable base = getBase(obj, index);
1739
base.put(index, obj, value);
1743
* Removes the property from an object or its prototype chain.
1745
* Searches for a property with <code>name</code> in obj or
1746
* its prototype chain. If it is found, the object's delete
1748
* @param obj a JavaScript object
1749
* @param name a property name
1750
* @return true if the property doesn't exist or was successfully removed
1753
public static boolean deleteProperty(Scriptable obj, String name)
1755
Scriptable base = getBase(obj, name);
1759
return !base.has(name, obj);
1763
* Removes the property from an object or its prototype chain.
1765
* Searches for a property with <code>index</code> in obj or
1766
* its prototype chain. If it is found, the object's delete
1768
* @param obj a JavaScript object
1769
* @param index a property index
1770
* @return true if the property doesn't exist or was successfully removed
1773
public static boolean deleteProperty(Scriptable obj, int index)
1775
Scriptable base = getBase(obj, index);
1779
return !base.has(index, obj);
1783
* Returns an array of all ids from an object and its prototypes.
1785
* @param obj a JavaScript object
1786
* @return an array of all ids from all object in the prototype chain.
1787
* If a given id occurs multiple times in the prototype chain,
1788
* it will occur only once in this list.
1791
public static Object[] getPropertyIds(Scriptable obj)
1794
return ScriptRuntime.emptyArgs;
1796
Object[] result = obj.getIds();
1797
ObjToIntMap map = null;
1799
obj = obj.getPrototype();
1803
Object[] ids = obj.getIds();
1804
if (ids.length == 0) {
1808
if (result.length == 0) {
1812
map = new ObjToIntMap(result.length + ids.length);
1813
for (int i = 0; i != result.length; ++i) {
1814
map.intern(result[i]);
1816
result = null; // Allow to GC the result
1818
for (int i = 0; i != ids.length; ++i) {
1823
result = map.getKeys();
1829
* Call a method of an object.
1830
* @param obj the JavaScript object
1831
* @param methodName the name of the function property
1832
* @param args the arguments for the call
1834
* @see Context#getCurrentContext()
1836
public static Object callMethod(Scriptable obj, String methodName,
1839
return callMethod(null, obj, methodName, args);
1843
* Call a method of an object.
1844
* @param cx the Context object associated with the current thread.
1845
* @param obj the JavaScript object
1846
* @param methodName the name of the function property
1847
* @param args the arguments for the call
1849
public static Object callMethod(Context cx, Scriptable obj,
1853
Object funObj = getProperty(obj, methodName);
1854
if (!(funObj instanceof Function)) {
1855
throw ScriptRuntime.notFunctionError(obj, methodName);
1857
Function fun = (Function)funObj;
1858
// XXX: What should be the scope when calling funObj?
1859
// The following favor scope stored in the object on the assumption
1860
// that is more useful especially under dynamic scope setup.
1861
// An alternative is to check for dynamic scope flag
1862
// and use ScriptableObject.getTopLevelScope(fun) if the flag is not
1863
// set. But that require access to Context and messy code
1864
// so for now it is not checked.
1865
Scriptable scope = ScriptableObject.getTopLevelScope(obj);
1867
return fun.call(cx, scope, obj, args);
1869
return Context.call(null, fun, scope, obj, args);
1873
private static Scriptable getBase(Scriptable obj, String name)
1876
if (obj.has(name, obj))
1878
obj = obj.getPrototype();
1879
} while(obj != null);
1883
private static Scriptable getBase(Scriptable obj, int index)
1886
if (obj.has(index, obj))
1888
obj = obj.getPrototype();
1889
} while(obj != null);
1894
* Get arbitrary application-specific value associated with this object.
1895
* @param key key object to select particular value.
1896
* @see #associateValue(Object key, Object value)
1898
public final Object getAssociatedValue(Object key)
1900
Hashtable h = associatedValues;
1907
* Get arbitrary application-specific value associated with the top scope
1908
* of the given scope.
1909
* The method first calls {@link #getTopLevelScope(Scriptable scope)}
1910
* and then searches the prototype chain of the top scope for the first
1911
* object containing the associated value with the given key.
1913
* @param scope the starting scope.
1914
* @param key key object to select particular value.
1915
* @see #getAssociatedValue(Object key)
1917
public static Object getTopScopeValue(Scriptable scope, Object key)
1919
scope = ScriptableObject.getTopLevelScope(scope);
1921
if (scope instanceof ScriptableObject) {
1922
ScriptableObject so = (ScriptableObject)scope;
1923
Object value = so.getAssociatedValue(key);
1924
if (value != null) {
1928
scope = scope.getPrototype();
1929
if (scope == null) {
1936
* Associate arbitrary application-specific value with this object.
1937
* Value can only be associated with the given object and key only once.
1938
* The method ignores any subsequent attempts to change the already
1940
* <p> The associated values are not serialized.
1941
* @param key key object to select particular value.
1942
* @param value the value to associate
1943
* @return the passed value if the method is called first time for the
1944
* given key or old value for any subsequent calls.
1945
* @see #getAssociatedValue(Object key)
1947
public final Object associateValue(Object key, Object value)
1949
if (value == null) throw new IllegalArgumentException();
1950
Hashtable h = associatedValues;
1952
synchronized (this) {
1953
h = associatedValues;
1955
h = new Hashtable();
1956
associatedValues = h;
1960
return Kit.initHash(h, key, value);
1963
private Object getImpl(String name, int index, Scriptable start)
1965
Slot slot = getSlot(name, index, SLOT_QUERY);
1967
return Scriptable.NOT_FOUND;
1969
if (!(slot instanceof GetterSlot)) {
1972
Object getterObj = ((GetterSlot)slot).getter;
1973
if (getterObj != null) {
1974
if (getterObj instanceof MemberBox) {
1975
MemberBox nativeGetter = (MemberBox)getterObj;
1978
if (nativeGetter.delegateTo == null) {
1980
args = ScriptRuntime.emptyArgs;
1982
getterThis = nativeGetter.delegateTo;
1983
args = new Object[] { start };
1985
return nativeGetter.invoke(getterThis, args);
1987
Function f = (Function)getterObj;
1988
Context cx = Context.getContext();
1989
return f.call(cx, f.getParentScope(), start,
1990
ScriptRuntime.emptyArgs);
1993
Object value = slot.value;
1994
if (value instanceof LazilyLoadedCtor) {
1995
LazilyLoadedCtor initializer = (LazilyLoadedCtor)value;
1999
value = initializer.getValue();
2012
* @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means
2013
* defineConstProperty. READONLY means const initialization expression.
2014
* @return false if this != start and no slot was found. true if this == start
2015
* or this != start and a READONLY slot was found.
2017
private boolean putImpl(String name, int index, Scriptable start,
2018
Object value, int constFlag)
2021
if (this != start) {
2022
slot = getSlot(name, index, SLOT_QUERY);
2027
checkNotSealed(name, index);
2028
// either const hoisted declaration or initialization
2029
if (constFlag != EMPTY) {
2030
slot = getSlot(name, index, SLOT_MODIFY_CONST);
2031
int attr = slot.getAttributes();
2032
if ((attr & READONLY) == 0)
2033
throw Context.reportRuntimeError1("msg.var.redecl", name);
2034
if ((attr & UNINITIALIZED_CONST) != 0) {
2036
// clear the bit on const initialization
2037
if (constFlag != UNINITIALIZED_CONST)
2038
slot.setAttributes(attr & ~UNINITIALIZED_CONST);
2042
slot = getSlot(name, index, SLOT_MODIFY);
2044
if ((slot.getAttributes() & READONLY) != 0)
2046
if (slot instanceof GetterSlot) {
2047
Object setterObj = ((GetterSlot)slot).setter;
2048
if (setterObj != null) {
2049
Context cx = Context.getContext();
2050
if (setterObj instanceof MemberBox) {
2051
MemberBox nativeSetter = (MemberBox)setterObj;
2052
Class pTypes[] = nativeSetter.argTypes;
2053
// XXX: cache tag since it is already calculated in
2055
Class valueType = pTypes[pTypes.length - 1];
2056
int tag = FunctionObject.getTypeTag(valueType);
2057
Object actualArg = FunctionObject.convertArg(cx, start,
2061
if (nativeSetter.delegateTo == null) {
2063
args = new Object[] { actualArg };
2065
setterThis = nativeSetter.delegateTo;
2066
args = new Object[] { start, actualArg };
2068
nativeSetter.invoke(setterThis, args);
2070
Function f = (Function)setterObj;
2071
f.call(cx, f.getParentScope(), start,
2072
new Object[] { value });
2077
if (this == start) {
2085
private Slot findAttributeSlot(String name, int index, int accessType)
2087
Slot slot = getSlot(name, index, accessType);
2089
String str = (name != null ? name : Integer.toString(index));
2090
throw Context.reportRuntimeError1("msg.prop.not.found", str);
2096
* Locate the slot with given name or index.
2098
* @param name property name or null if slot holds spare array index.
2099
* @param index index or 0 if slot holds property name.
2101
private Slot getSlot(String name, int index, int accessType)
2105
// Query last access cache and check that it was not deleted.
2110
if (name != slot.name)
2111
break lastAccessCheck;
2112
// No String.equals here as successful slot search update
2113
// name object with fresh reference of the same string.
2115
if (slot.name != null || index != slot.indexOrHash)
2116
break lastAccessCheck;
2119
if (slot.wasDeleted != 0)
2120
break lastAccessCheck;
2122
if (accessType == SLOT_MODIFY_GETTER_SETTER &&
2123
!(slot instanceof GetterSlot))
2124
break lastAccessCheck;
2129
slot = accessSlot(name, index, accessType);
2137
private Slot accessSlot(String name, int index, int accessType)
2139
int indexOrHash = (name != null ? name.hashCode() : index);
2141
if (accessType == SLOT_QUERY ||
2142
accessType == SLOT_MODIFY ||
2143
accessType == SLOT_MODIFY_CONST ||
2144
accessType == SLOT_MODIFY_GETTER_SETTER)
2146
// Check the hashtable without using synchronization
2148
Slot[] slotsLocalRef = slots; // Get stable local reference
2149
if (slotsLocalRef == null) {
2150
if (accessType == SLOT_QUERY)
2153
int tableSize = slotsLocalRef.length;
2154
int slotIndex = getSlotIndex(tableSize, indexOrHash);
2155
Slot slot = slotsLocalRef[slotIndex];
2156
while (slot != null) {
2157
String sname = slot.name;
2158
if (sname != null) {
2161
if (name != null && indexOrHash == slot.indexOrHash) {
2162
if (name.equals(sname)) {
2163
// This will avoid calling String.equals when
2164
// slot is accessed with same string object
2170
} else if (name == null &&
2171
indexOrHash == slot.indexOrHash) {
2176
if (accessType == SLOT_QUERY) {
2178
} else if (accessType == SLOT_MODIFY) {
2181
} else if (accessType == SLOT_MODIFY_GETTER_SETTER) {
2182
if (slot instanceof GetterSlot)
2184
} else if (accessType == SLOT_MODIFY_CONST) {
2190
// A new slot has to be inserted or the old has to be replaced
2191
// by GetterSlot. Time to synchronize.
2193
synchronized (this) {
2194
// Refresh local ref if another thread triggered grow
2195
slotsLocalRef = slots;
2198
// Always throw away old slots if any on empty insert
2199
slotsLocalRef = new Slot[5];
2200
slots = slotsLocalRef;
2201
insertPos = getSlotIndex(slotsLocalRef.length, indexOrHash);
2203
int tableSize = slotsLocalRef.length;
2204
insertPos = getSlotIndex(tableSize, indexOrHash);
2205
Slot prev = slotsLocalRef[insertPos];
2207
while (slot != null) {
2208
if (slot.indexOrHash == indexOrHash &&
2209
(slot.name == name ||
2210
(name != null && name.equals(slot.name))))
2219
// Another thread just added a slot with same
2220
// name/index before this one entered synchronized
2221
// block. This is a race in application code and
2222
// probably indicates bug there. But for the hashtable
2223
// implementation it is harmless with the only
2224
// complication is the need to replace the added slot
2225
// if we need GetterSlot and the old one is not.
2226
if (accessType == SLOT_MODIFY_GETTER_SETTER &&
2227
!(slot instanceof GetterSlot))
2229
GetterSlot newSlot = new GetterSlot(name, indexOrHash,
2230
slot.getAttributes());
2231
newSlot.value = slot.value;
2232
newSlot.next = slot.next;
2234
slotsLocalRef[insertPos] = newSlot;
2236
prev.next = newSlot;
2238
slot.wasDeleted = (byte)1;
2239
if (slot == lastAccess) {
2240
lastAccess = REMOVED;
2243
} else if (accessType == SLOT_MODIFY_CONST) {
2249
// Check if the table is not too full before inserting.
2250
if (4 * (count + 1) > 3 * slotsLocalRef.length) {
2251
slotsLocalRef = new Slot[slotsLocalRef.length * 2 + 1];
2252
copyTable(slots, slotsLocalRef, count);
2253
slots = slotsLocalRef;
2254
insertPos = getSlotIndex(slotsLocalRef.length,
2259
Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER
2260
? new GetterSlot(name, indexOrHash, 0)
2261
: new Slot(name, indexOrHash, 0));
2262
if (accessType == SLOT_MODIFY_CONST)
2263
newSlot.setAttributes(CONST);
2265
addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos);
2269
} else if (accessType == SLOT_REMOVE) {
2270
synchronized (this) {
2271
Slot[] slotsLocalRef = slots;
2273
int tableSize = slots.length;
2274
int slotIndex = getSlotIndex(tableSize, indexOrHash);
2275
Slot prev = slotsLocalRef[slotIndex];
2277
while (slot != null) {
2278
if (slot.indexOrHash == indexOrHash &&
2279
(slot.name == name ||
2280
(name != null && name.equals(slot.name))))
2287
if (slot != null && (slot.getAttributes() & PERMANENT) == 0) {
2290
slotsLocalRef[slotIndex] = slot.next;
2292
prev.next = slot.next;
2294
// Mark the slot as removed to handle a case when
2295
// another thread manages to put just removed slot
2296
// into lastAccess cache.
2297
slot.wasDeleted = (byte)1;
2298
if (slot == lastAccess) {
2299
lastAccess = REMOVED;
2307
throw Kit.codeBug();
2311
private static int getSlotIndex(int tableSize, int indexOrHash)
2313
return (indexOrHash & 0x7fffffff) % tableSize;
2316
// Must be inside synchronized (this)
2317
private static void copyTable(Slot[] slots, Slot[] newSlots, int count)
2319
if (count == 0) throw Kit.codeBug();
2321
int tableSize = newSlots.length;
2322
int i = slots.length;
2325
Slot slot = slots[i];
2326
while (slot != null) {
2327
int insertPos = getSlotIndex(tableSize, slot.indexOrHash);
2328
Slot next = slot.next;
2329
addKnownAbsentSlot(newSlots, slot, insertPos);
2339
* Add slot with keys that are known to absent from the table.
2340
* This is an optimization to use when inserting into empty table,
2341
* after table growth or during deserialization.
2343
private static void addKnownAbsentSlot(Slot[] slots, Slot slot, int insertPos)
2345
if (slots[insertPos] == null) {
2346
slots[insertPos] = slot;
2348
Slot prev = slots[insertPos];
2349
while (prev.next != null) {
2356
Object[] getIds(boolean getAll) {
2358
Object[] a = ScriptRuntime.emptyArgs;
2362
for (int i=0; i < s.length; i++) {
2364
while (slot != null) {
2365
if (getAll || (slot.getAttributes() & DONTENUM) == 0) {
2367
a = new Object[s.length];
2368
a[c++] = (slot.name != null ? (Object) slot.name
2369
: new Integer(slot.indexOrHash));
2376
Object[] result = new Object[c];
2377
System.arraycopy(a, 0, result, 0, c);
2381
private synchronized void writeObject(ObjectOutputStream out)
2384
out.defaultWriteObject();
2385
int objectsCount = count;
2386
if (objectsCount < 0) {
2387
// "this" was sealed
2388
objectsCount = ~objectsCount;
2390
if (objectsCount == 0) {
2393
out.writeInt(slots.length);
2394
for (int i = 0; i < slots.length; ++i) {
2395
Slot slot = slots[i];
2396
while (slot != null) {
2397
out.writeObject(slot);
2399
if (--objectsCount == 0)
2406
private void readObject(ObjectInputStream in)
2407
throws IOException, ClassNotFoundException
2409
in.defaultReadObject();
2410
lastAccess = REMOVED;
2412
int tableSize = in.readInt();
2413
if (tableSize != 0) {
2414
slots = new Slot[tableSize];
2415
int objectsCount = count;
2416
if (objectsCount < 0) {
2417
// "this" was sealed
2418
objectsCount = ~objectsCount;
2420
for (int i = 0; i != objectsCount; ++i) {
2421
Slot slot = (Slot)in.readObject();
2422
int slotIndex = getSlotIndex(tableSize, slot.indexOrHash);
2423
addKnownAbsentSlot(slots, slot, slotIndex);