1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
\r
3 * The contents of this file are subject to the Netscape Public
\r
4 * License Version 1.1 (the "License"); you may not use this file
\r
5 * except in compliance with the License. You may obtain a copy of
\r
6 * the License at http://www.mozilla.org/NPL/
\r
8 * Software distributed under the License is distributed on an "AS
\r
9 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
\r
10 * implied. See the License for the specific language governing
\r
11 * rights and limitations under the License.
\r
13 * The Original Code is Rhino code, released
\r
16 * The Initial Developer of the Original Code is Netscape
\r
17 * Communications Corporation. Portions created by Netscape are
\r
18 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
\r
26 * Alternatively, the contents of this file may be used under the
\r
27 * terms of the GNU Public License (the "GPL"), in which case the
\r
28 * provisions of the GPL are applicable instead of those above.
\r
29 * If you wish to allow use of your version of this file only
\r
30 * under the terms of the GPL and not to allow others to use your
\r
31 * version of this file under the NPL, indicate your decision by
\r
32 * deleting the provisions above and replace them with the notice
\r
33 * and other provisions required by the GPL. If you do not delete
\r
34 * the provisions above, a recipient may use your version of this
\r
35 * file under either the NPL or the GPL.
\r
40 package org.mozilla.javascript;
\r
42 import java.lang.reflect.*;
\r
43 import java.util.Hashtable;
\r
46 * This is the default implementation of the Scriptable interface. This
\r
47 * class provides convenient default behavior that makes it easier to
\r
48 * define host objects.
\r
50 * Various properties and methods of JavaScript objects can be conveniently
\r
51 * defined using methods of ScriptableObject.
\r
53 * Classes extending ScriptableObject must define the getClassName method.
\r
55 * @see org.mozilla.javascript.Scriptable
\r
56 * @author Norris Boyd
\r
59 public abstract class ScriptableObject implements Scriptable {
\r
62 * The empty property attribute.
\r
64 * Used by getAttributes() and setAttributes().
\r
66 * @see org.mozilla.javascript.ScriptableObject#getAttributes
\r
67 * @see org.mozilla.javascript.ScriptableObject#setAttributes
\r
69 public static final int EMPTY = 0x00;
\r
72 * Property attribute indicating assignment to this property is ignored.
\r
74 * @see org.mozilla.javascript.ScriptableObject#put
\r
75 * @see org.mozilla.javascript.ScriptableObject#getAttributes
\r
76 * @see org.mozilla.javascript.ScriptableObject#setAttributes
\r
78 public static final int READONLY = 0x01;
\r
81 * Property attribute indicating property is not enumerated.
\r
83 * Only enumerated properties will be returned by getIds().
\r
85 * @see org.mozilla.javascript.ScriptableObject#getIds
\r
86 * @see org.mozilla.javascript.ScriptableObject#getAttributes
\r
87 * @see org.mozilla.javascript.ScriptableObject#setAttributes
\r
89 public static final int DONTENUM = 0x02;
\r
92 * Property attribute indicating property cannot be deleted.
\r
94 * @see org.mozilla.javascript.ScriptableObject#delete
\r
95 * @see org.mozilla.javascript.ScriptableObject#getAttributes
\r
96 * @see org.mozilla.javascript.ScriptableObject#setAttributes
\r
98 public static final int PERMANENT = 0x04;
\r
101 * Return the name of the class.
\r
103 * This is typically the same name as the constructor.
\r
104 * Classes extending ScriptableObject must implement this abstract
\r
107 public abstract String getClassName();
\r
110 * Returns true if the named property is defined.
\r
112 * @param name the name of the property
\r
113 * @param start the object in which the lookup began
\r
114 * @return true if and only if the property was found in the object
\r
116 public boolean has(String name, Scriptable start) {
\r
117 return getSlot(name, name.hashCode(), false) != null;
\r
121 * Returns true if the property index is defined.
\r
123 * @param index the numeric index for the property
\r
124 * @param start the object in which the lookup began
\r
125 * @return true if and only if the property was found in the object
\r
127 public boolean has(int index, Scriptable start) {
\r
128 return getSlot(null, index, false) != null;
\r
132 * Returns the value of the named property or NOT_FOUND.
\r
134 * If the property was created using defineProperty, the
\r
135 * appropriate getter method is called.
\r
137 * @param name the name of the property
\r
138 * @param start the object in which the lookup began
\r
139 * @return the value of the property (may be null), or NOT_FOUND
\r
141 public Object get(String name, Scriptable start) {
\r
142 Slot slot = lastAccess; // Get local copy
\r
143 if (name == slot.stringKey) {
\r
144 if (slot.wasDeleted == 0) { return slot.value; }
\r
146 int hashCode = name.hashCode();
\r
147 slot = getSlot(name, hashCode, false);
\r
149 return Scriptable.NOT_FOUND;
\r
150 if ((slot.flags & Slot.HAS_GETTER) != 0) {
\r
151 GetterSlot getterSlot = (GetterSlot) slot;
\r
153 if (getterSlot.delegateTo == null) {
\r
154 // Walk the prototype chain to find an appropriate
\r
155 // object to invoke the getter on.
\r
156 Class clazz = getterSlot.getter.getDeclaringClass();
\r
157 while (!clazz.isInstance(start)) {
\r
158 start = start.getPrototype();
\r
159 if (start == null) {
\r
164 return getterSlot.getter.invoke(start, ScriptRuntime.emptyArgs);
\r
166 Object[] args = { this };
\r
167 return getterSlot.getter.invoke(getterSlot.delegateTo, args);
\r
169 catch (InvocationTargetException e) {
\r
170 throw WrappedException.wrapException(e);
\r
172 catch (IllegalAccessException e) {
\r
173 throw WrappedException.wrapException(e);
\r
176 // Here stringKey.equals(name) holds, but it can be that
\r
177 // slot.stringKey != name. To make last name cache work, need
\r
178 // to change the key
\r
179 slot.stringKey = name;
\r
187 * Returns the value of the indexed property or NOT_FOUND.
\r
189 * @param index the numeric index for the property
\r
190 * @param start the object in which the lookup began
\r
191 * @return the value of the property (may be null), or NOT_FOUND
\r
193 public Object get(int index, Scriptable start) {
\r
194 Slot slot = getSlot(null, index, false);
\r
196 return Scriptable.NOT_FOUND;
\r
201 * Sets the value of the named property, creating it if need be.
\r
203 * If the property was created using defineProperty, the
\r
204 * appropriate setter method is called. <p>
\r
206 * If the property's attributes include READONLY, no action is
\r
208 * This method will actually set the property in the start
\r
211 * @param name the name of the property
\r
212 * @param start the object whose property is being set
\r
213 * @param value value to set the property to
\r
215 public void put(String name, Scriptable start, Object value) {
\r
216 int hash = name.hashCode();
\r
217 Slot slot = getSlot(name, hash, false);
\r
218 if (slot == null) {
\r
219 if (start != this) {
\r
220 start.put(name, start, value);
\r
223 slot = getSlotToSet(name, hash, false);
\r
225 if ((slot.attributes & ScriptableObject.READONLY) != 0)
\r
227 if ((slot.flags & Slot.HAS_SETTER) != 0) {
\r
228 GetterSlot getterSlot = (GetterSlot) slot;
\r
230 Class pTypes[] = getterSlot.setter.getParameterTypes();
\r
231 Class desired = pTypes[pTypes.length - 1];
\r
233 = FunctionObject.convertArg(start, value, desired);
\r
234 if (getterSlot.delegateTo == null) {
\r
235 // Walk the prototype chain to find an appropriate
\r
236 // object to invoke the setter on.
\r
237 Object[] arg = { actualArg };
\r
238 Class clazz = getterSlot.setter.getDeclaringClass();
\r
239 while (!clazz.isInstance(start)) {
\r
240 start = start.getPrototype();
\r
241 if (start == null) {
\r
246 Object v = getterSlot.setter.invoke(start, arg);
\r
247 if (getterSlot.setterReturnsValue) {
\r
249 if (!(v instanceof Method))
\r
254 Object[] args = { this, actualArg };
\r
255 Object v = getterSlot.setter.invoke(getterSlot.delegateTo, args);
\r
256 if (getterSlot.setterReturnsValue) {
\r
258 if (!(v instanceof Method))
\r
263 catch (InvocationTargetException e) {
\r
264 throw WrappedException.wrapException(e);
\r
266 catch (IllegalAccessException e) {
\r
267 throw WrappedException.wrapException(e);
\r
270 if (this == start) {
\r
271 slot.value = value;
\r
273 slot.stringKey = name;
\r
276 start.put(name, start, value);
\r
281 * Sets the value of the indexed property, creating it if need be.
\r
283 * @param index the numeric index for the property
\r
284 * @param start the object whose property is being set
\r
285 * @param value value to set the property to
\r
287 public void put(int index, Scriptable start, Object value) {
\r
288 Slot slot = getSlot(null, index, false);
\r
289 if (slot == null) {
\r
290 if (start != this) {
\r
291 start.put(index, start, value);
\r
294 slot = getSlotToSet(null, index, false);
\r
296 if ((slot.attributes & ScriptableObject.READONLY) != 0)
\r
298 if (this == start) {
\r
299 slot.value = value;
\r
301 start.put(index, start, value);
\r
306 * Removes a named property from the object.
\r
308 * If the property is not found, or it has the PERMANENT attribute,
\r
309 * no action is taken.
\r
311 * @param name the name of the property
\r
313 public void delete(String name) {
\r
314 removeSlot(name, name.hashCode());
\r
318 * Removes the indexed property from the object.
\r
320 * If the property is not found, or it has the PERMANENT attribute,
\r
321 * no action is taken.
\r
323 * @param index the numeric index for the property
\r
325 public void delete(int index) {
\r
326 removeSlot(null, index);
\r
330 * Get the attributes of a named property.
\r
332 * The property is specified by <code>name</code>
\r
333 * as defined for <code>has</code>.<p>
\r
335 * @param name the identifier for the property
\r
336 * @param start the object in which the lookup began
\r
337 * @return the bitset of attributes
\r
338 * @exception PropertyException if the named property
\r
340 * @see org.mozilla.javascript.ScriptableObject#has
\r
341 * @see org.mozilla.javascript.ScriptableObject#READONLY
\r
342 * @see org.mozilla.javascript.ScriptableObject#DONTENUM
\r
343 * @see org.mozilla.javascript.ScriptableObject#PERMANENT
\r
344 * @see org.mozilla.javascript.ScriptableObject#EMPTY
\r
346 public int getAttributes(String name, Scriptable start)
\r
347 throws PropertyException
\r
349 Slot slot = getSlot(name, name.hashCode(), false);
\r
350 if (slot == null) {
\r
351 throw PropertyException.withMessage0("msg.prop.not.found");
\r
353 return slot.attributes;
\r
357 * Get the attributes of an indexed property.
\r
359 * @param index the numeric index for the property
\r
360 * @param start the object in which the lookup began
\r
361 * @exception PropertyException if the indexed property
\r
363 * @return the bitset of attributes
\r
364 * @see org.mozilla.javascript.ScriptableObject#has
\r
365 * @see org.mozilla.javascript.ScriptableObject#READONLY
\r
366 * @see org.mozilla.javascript.ScriptableObject#DONTENUM
\r
367 * @see org.mozilla.javascript.ScriptableObject#PERMANENT
\r
368 * @see org.mozilla.javascript.ScriptableObject#EMPTY
\r
370 public int getAttributes(int index, Scriptable start)
\r
371 throws PropertyException
\r
373 Slot slot = getSlot(null, index, false);
\r
374 if (slot == null) {
\r
375 throw PropertyException.withMessage0("msg.prop.not.found");
\r
377 return slot.attributes;
\r
381 * Set the attributes of a named property.
\r
383 * The property is specified by <code>name</code>
\r
384 * as defined for <code>has</code>.<p>
\r
386 * The possible attributes are READONLY, DONTENUM,
\r
387 * and PERMANENT. Combinations of attributes
\r
388 * are expressed by the bitwise OR of attributes.
\r
389 * EMPTY is the state of no attributes set. Any unused
\r
390 * bits are reserved for future use.
\r
392 * @param name the name of the property
\r
393 * @param start the object in which the lookup began
\r
394 * @param attributes the bitset of attributes
\r
395 * @exception PropertyException if the named property
\r
397 * @see org.mozilla.javascript.Scriptable#has
\r
398 * @see org.mozilla.javascript.ScriptableObject#READONLY
\r
399 * @see org.mozilla.javascript.ScriptableObject#DONTENUM
\r
400 * @see org.mozilla.javascript.ScriptableObject#PERMANENT
\r
401 * @see org.mozilla.javascript.ScriptableObject#EMPTY
\r
403 public void setAttributes(String name, Scriptable start,
\r
405 throws PropertyException
\r
407 final int mask = READONLY | DONTENUM | PERMANENT;
\r
408 attributes &= mask; // mask out unused bits
\r
409 Slot slot = getSlot(name, name.hashCode(), false);
\r
410 if (slot == null) {
\r
411 throw PropertyException.withMessage0("msg.prop.not.found");
\r
413 slot.attributes = (short) attributes;
\r
417 * Set the attributes of an indexed property.
\r
419 * @param index the numeric index for the property
\r
420 * @param start the object in which the lookup began
\r
421 * @param attributes the bitset of attributes
\r
422 * @exception PropertyException if the indexed property
\r
424 * @see org.mozilla.javascript.Scriptable#has
\r
425 * @see org.mozilla.javascript.ScriptableObject#READONLY
\r
426 * @see org.mozilla.javascript.ScriptableObject#DONTENUM
\r
427 * @see org.mozilla.javascript.ScriptableObject#PERMANENT
\r
428 * @see org.mozilla.javascript.ScriptableObject#EMPTY
\r
430 public void setAttributes(int index, Scriptable start,
\r
432 throws PropertyException
\r
434 Slot slot = getSlot(null, index, false);
\r
435 if (slot == null) {
\r
436 throw PropertyException.withMessage0("msg.prop.not.found");
\r
438 slot.attributes = (short) attributes;
\r
442 * Returns the prototype of the object.
\r
444 public Scriptable getPrototype() {
\r
449 * Sets the prototype of the object.
\r
451 public void setPrototype(Scriptable m) {
\r
456 * Returns the parent (enclosing) scope of the object.
\r
458 public Scriptable getParentScope() {
\r
463 * Sets the parent (enclosing) scope of the object.
\r
465 public void setParentScope(Scriptable m) {
\r
470 * Returns an array of ids for the properties of the object.
\r
472 * <p>Any properties with the attribute DONTENUM are not listed. <p>
\r
474 * @return an array of java.lang.Objects with an entry for every
\r
475 * listed property. Properties accessed via an integer index will
\r
476 * have a corresponding
\r
477 * Integer entry in the returned array. Properties accessed by
\r
478 * a String will have a String entry in the returned array.
\r
480 public Object[] getIds() {
\r
481 return getIds(false);
\r
485 * Returns an array of ids for the properties of the object.
\r
487 * <p>All properties, even those with attribute DONTENUM, are listed. <p>
\r
489 * @return an array of java.lang.Objects with an entry for every
\r
490 * listed property. Properties accessed via an integer index will
\r
491 * have a corresponding
\r
492 * Integer entry in the returned array. Properties accessed by
\r
493 * a String will have a String entry in the returned array.
\r
495 public Object[] getAllIds() {
\r
496 return getIds(true);
\r
500 * Implements the [[DefaultValue]] internal method.
\r
502 * <p>Note that the toPrimitive conversion is a no-op for
\r
503 * every type other than Object, for which [[DefaultValue]]
\r
504 * is called. See ECMA 9.1.<p>
\r
506 * A <code>hint</code> of null means "no hint".
\r
508 * @param typeHint the type hint
\r
509 * @return the default value for the object
\r
511 * See ECMA 8.6.2.6.
\r
513 public Object getDefaultValue(Class typeHint) {
\r
517 for (int i=0; i < 2; i++) {
\r
518 if (typeHint == ScriptRuntime.StringClass ? i == 0 : i == 1) {
\r
519 Object v = getProperty(this, "toString");
\r
520 if (!(v instanceof Function))
\r
522 Function fun = (Function) v;
\r
524 cx = Context.getContext();
\r
525 val = fun.call(cx, fun.getParentScope(), this,
\r
526 ScriptRuntime.emptyArgs);
\r
529 if (typeHint == null)
\r
530 hint = "undefined";
\r
531 else if (typeHint == ScriptRuntime.StringClass)
\r
533 else if (typeHint == ScriptRuntime.ScriptableClass)
\r
535 else if (typeHint == ScriptRuntime.FunctionClass)
\r
537 else if (typeHint == ScriptRuntime.BooleanClass ||
\r
538 typeHint == Boolean.TYPE)
\r
540 else if (typeHint == ScriptRuntime.NumberClass ||
\r
541 typeHint == ScriptRuntime.ByteClass ||
\r
542 typeHint == Byte.TYPE ||
\r
543 typeHint == ScriptRuntime.ShortClass ||
\r
544 typeHint == Short.TYPE ||
\r
545 typeHint == ScriptRuntime.IntegerClass ||
\r
546 typeHint == Integer.TYPE ||
\r
547 typeHint == ScriptRuntime.FloatClass ||
\r
548 typeHint == Float.TYPE ||
\r
549 typeHint == ScriptRuntime.DoubleClass ||
\r
550 typeHint == Double.TYPE)
\r
553 throw Context.reportRuntimeError1(
\r
554 "msg.invalid.type", typeHint.toString());
\r
556 Object v = getProperty(this, "valueOf");
\r
557 if (!(v instanceof Function))
\r
559 Function fun = (Function) v;
\r
560 Object[] args = { hint };
\r
562 cx = Context.getContext();
\r
563 val = fun.call(cx, fun.getParentScope(), this, args);
\r
565 if (val != null && (val == Undefined.instance ||
\r
566 !(val instanceof Scriptable) ||
\r
567 typeHint == Scriptable.class ||
\r
568 typeHint == Function.class))
\r
572 if (val instanceof NativeJavaObject) {
\r
573 // Let a wrapped java.lang.String pass for a primitive
\r
575 Object u = ((Wrapper) val).unwrap();
\r
576 if (u instanceof String)
\r
580 // fall through to error
\r
582 catch (JavaScriptException jse) {
\r
583 // fall through to error
\r
585 Object arg = (typeHint == null) ? "undefined" : typeHint.toString();
\r
586 throw NativeGlobal.typeError1("msg.default.value", arg, this);
\r
590 * Implements the instanceof operator.
\r
592 * <p>This operator has been proposed to ECMA.
\r
594 * @param instance The value that appeared on the LHS of the instanceof
\r
596 * @return true if "this" appears in value's prototype chain
\r
599 public boolean hasInstance(Scriptable instance) {
\r
600 // Default for JS objects (other than Function) is to do prototype
\r
601 // chasing. This will be overridden in NativeFunction and non-JS objects.
\r
603 return ScriptRuntime.jsDelegatesTo(instance, this);
\r
607 * Defines JavaScript objects from a Java class that implements Scriptable.
\r
609 * If the given class has a method
\r
611 * static void init(Context cx, Scriptable scope, boolean sealed);</pre>
\r
613 * or its compatibility form
\r
615 * static void init(Scriptable scope);</pre>
\r
617 * then it is invoked and no further initialization is done.<p>
\r
619 * However, if no such a method is found, then the class's constructors and
\r
620 * methods are used to initialize a class in the following manner.<p>
\r
622 * First, the zero-parameter constructor of the class is called to
\r
623 * create the prototype. If no such constructor exists,
\r
624 * a ClassDefinitionException is thrown. <p>
\r
626 * Next, all methods are scanned for special prefixes that indicate that they
\r
627 * have special meaning for defining JavaScript objects.
\r
628 * These special prefixes are
\r
630 * <li><code>jsFunction_</code> for a JavaScript function
\r
631 * <li><code>jsStaticFunction_</code> for a JavaScript function that
\r
632 * is a property of the constructor
\r
633 * <li><code>jsGet_</code> for a getter of a JavaScript property
\r
634 * <li><code>jsSet_</code> for a setter of a JavaScript property
\r
635 * <li><code>jsConstructor</code> for a JavaScript function that
\r
636 * is the constructor
\r
639 * If the method's name begins with "jsFunction_", a JavaScript function
\r
640 * is created with a name formed from the rest of the Java method name
\r
641 * following "jsFunction_". So a Java method named "jsFunction_foo" will
\r
642 * define a JavaScript method "foo". Calling this JavaScript function
\r
643 * will cause the Java method to be called. The parameters of the method
\r
644 * must be of number and types as defined by the FunctionObject class.
\r
645 * The JavaScript function is then added as a property
\r
646 * of the prototype. <p>
\r
648 * If the method's name begins with "jsStaticFunction_", it is handled
\r
649 * similarly except that the resulting JavaScript function is added as a
\r
650 * property of the constructor object. The Java method must be static.
\r
652 * If the method's name begins with "jsGet_" or "jsSet_", the method is
\r
653 * considered to define a property. Accesses to the defined property
\r
654 * will result in calls to these getter and setter methods. If no
\r
655 * setter is defined, the property is defined as READONLY.<p>
\r
657 * If the method's name is "jsConstructor", the method is
\r
658 * considered to define the body of the constructor. Only one
\r
659 * method of this name may be defined.
\r
660 * If no method is found that can serve as constructor, a Java
\r
661 * constructor will be selected to serve as the JavaScript
\r
662 * constructor in the following manner. If the class has only one
\r
663 * Java constructor, that constructor is used to define
\r
664 * the JavaScript constructor. If the the class has two constructors,
\r
665 * one must be the zero-argument constructor (otherwise an
\r
666 * ClassDefinitionException would have already been thrown
\r
667 * when the prototype was to be created). In this case
\r
668 * the Java constructor with one or more parameters will be used
\r
669 * to define the JavaScript constructor. If the class has three
\r
670 * or more constructors, an ClassDefinitionException
\r
671 * will be thrown.<p>
\r
673 * Finally, if there is a method
\r
675 * static void finishInit(Scriptable scope, FunctionObject constructor,
\r
676 * Scriptable prototype)</pre>
\r
678 * it will be called to finish any initialization. The <code>scope</code>
\r
679 * argument will be passed, along with the newly created constructor and
\r
680 * the newly created prototype.<p>
\r
682 * @param scope The scope in which to define the constructor
\r
683 * @param clazz The Java class to use to define the JavaScript objects
\r
685 * @exception IllegalAccessException if access is not available
\r
686 * to a reflected class member
\r
687 * @exception InstantiationException if unable to instantiate
\r
689 * @exception InvocationTargetException if an exception is thrown
\r
690 * during execution of methods of the named class
\r
691 * @exception ClassDefinitionException if an appropriate
\r
692 * constructor cannot be found to create the prototype
\r
693 * @exception PropertyException if getter and setter
\r
694 * methods do not conform to the requirements of the
\r
695 * defineProperty method
\r
696 * @see org.mozilla.javascript.Function
\r
697 * @see org.mozilla.javascript.FunctionObject
\r
698 * @see org.mozilla.javascript.ScriptableObject#READONLY
\r
699 * @see org.mozilla.javascript.ScriptableObject#defineProperty
\r
701 public static void defineClass(Scriptable scope, Class clazz)
\r
702 throws IllegalAccessException, InstantiationException,
\r
703 InvocationTargetException, ClassDefinitionException,
\r
706 defineClass(scope, clazz, false);
\r
710 * Defines JavaScript objects from a Java class, optionally
\r
711 * allowing sealing.
\r
713 * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
\r
714 * except that sealing is allowed. An object that is sealed cannot have
\r
715 * properties added or removed. Note that sealing is not allowed in
\r
716 * the current ECMA/ISO language specification, but is likely for
\r
717 * the next version.
\r
719 * @param scope The scope in which to define the constructor
\r
720 * @param clazz The Java class to use to define the JavaScript objects
\r
721 * and properties. The class must implement Scriptable.
\r
722 * @param sealed whether or not to create sealed standard objects that
\r
723 * cannot be modified.
\r
724 * @exception IllegalAccessException if access is not available
\r
725 * to a reflected class member
\r
726 * @exception InstantiationException if unable to instantiate
\r
728 * @exception InvocationTargetException if an exception is thrown
\r
729 * during execution of methods of the named class
\r
730 * @exception ClassDefinitionException if an appropriate
\r
731 * constructor cannot be found to create the prototype
\r
732 * @exception PropertyException if getter and setter
\r
733 * methods do not conform to the requirements of the
\r
734 * defineProperty method
\r
737 public static void defineClass(Scriptable scope, Class clazz,
\r
739 throws IllegalAccessException, InstantiationException,
\r
740 InvocationTargetException, ClassDefinitionException,
\r
743 Method[] methods = FunctionObject.getMethodList(clazz);
\r
744 for (int i=0; i < methods.length; i++) {
\r
745 Method method = methods[i];
\r
746 if (!method.getName().equals("init"))
\r
748 Class[] parmTypes = method.getParameterTypes();
\r
749 if (parmTypes.length == 3 &&
\r
750 parmTypes[0] == ContextClass &&
\r
751 parmTypes[1] == ScriptRuntime.ScriptableClass &&
\r
752 parmTypes[2] == Boolean.TYPE &&
\r
753 Modifier.isStatic(method.getModifiers()))
\r
755 Object args[] = { Context.getContext(), scope,
\r
756 sealed ? Boolean.TRUE : Boolean.FALSE };
\r
757 method.invoke(null, args);
\r
760 if (parmTypes.length == 1 &&
\r
761 parmTypes[0] == ScriptRuntime.ScriptableClass &&
\r
762 Modifier.isStatic(method.getModifiers()))
\r
764 Object args[] = { scope };
\r
765 method.invoke(null, args);
\r
771 // If we got here, there isn't an "init" method with the right
\r
772 // parameter types.
\r
773 Hashtable exclusionList = getExclusionList();
\r
775 Constructor[] ctors = clazz.getConstructors();
\r
776 Constructor protoCtor = null;
\r
777 for (int i=0; i < ctors.length; i++) {
\r
778 if (ctors[i].getParameterTypes().length == 0) {
\r
779 protoCtor = ctors[i];
\r
783 if (protoCtor == null) {
\r
784 throw new ClassDefinitionException(
\r
785 Context.getMessage1("msg.zero.arg.ctor", clazz.getName()));
\r
788 Scriptable proto = (Scriptable)
\r
789 protoCtor.newInstance(ScriptRuntime.emptyArgs);
\r
790 proto.setPrototype(getObjectPrototype(scope));
\r
791 String className = proto.getClassName();
\r
793 // Find out whether there are any methods that begin with
\r
794 // "js". If so, then only methods that begin with special
\r
795 // prefixes will be defined as JavaScript entities.
\r
796 // The prefixes "js_" and "jsProperty_" are deprecated.
\r
797 final String genericPrefix = "js_";
\r
798 final String functionPrefix = "jsFunction_";
\r
799 final String staticFunctionPrefix = "jsStaticFunction_";
\r
800 final String propertyPrefix = "jsProperty_";
\r
801 final String getterPrefix = "jsGet_";
\r
802 final String setterPrefix = "jsSet_";
\r
803 final String ctorName = "jsConstructor";
\r
805 boolean hasPrefix = false;
\r
806 Method[] ctorMeths = FunctionObject.findMethods(clazz, ctorName);
\r
807 Member ctorMember = null;
\r
808 if (ctorMeths != null) {
\r
809 if (ctorMeths.length > 1) {
\r
810 throw new ClassDefinitionException(
\r
811 Context.getMessage2("msg.multiple.ctors",
\r
812 ctorMeths[0], ctorMeths[1]));
\r
814 ctorMember = ctorMeths[0];
\r
818 // Deprecated: look for functions with the same name as the class
\r
819 // and consider them constructors.
\r
820 for (int i=0; i < methods.length; i++) {
\r
821 String name = methods[i].getName();
\r
822 String prefix = null;
\r
823 if (!name.startsWith("js")) // common start to all prefixes
\r
825 else if (name.startsWith(genericPrefix))
\r
826 prefix = genericPrefix;
\r
827 else if (name.startsWith(functionPrefix))
\r
828 prefix = functionPrefix;
\r
829 else if (name.startsWith(staticFunctionPrefix))
\r
830 prefix = staticFunctionPrefix;
\r
831 else if (name.startsWith(propertyPrefix))
\r
832 prefix = propertyPrefix;
\r
833 else if (name.startsWith(getterPrefix))
\r
834 prefix = getterPrefix;
\r
835 else if (name.startsWith(setterPrefix))
\r
836 prefix = setterPrefix;
\r
837 if (prefix != null) {
\r
839 name = name.substring(prefix.length());
\r
841 if (name.equals(className)) {
\r
842 if (ctorMember != null) {
\r
843 throw new ClassDefinitionException(
\r
844 Context.getMessage2("msg.multiple.ctors",
\r
845 ctorMember, methods[i]));
\r
847 ctorMember = methods[i];
\r
851 if (ctorMember == null) {
\r
852 if (ctors.length == 1) {
\r
853 ctorMember = ctors[0];
\r
854 } else if (ctors.length == 2) {
\r
855 if (ctors[0].getParameterTypes().length == 0)
\r
856 ctorMember = ctors[1];
\r
857 else if (ctors[1].getParameterTypes().length == 0)
\r
858 ctorMember = ctors[0];
\r
860 if (ctorMember == null) {
\r
861 throw new ClassDefinitionException(
\r
862 Context.getMessage1("msg.ctor.multiple.parms",
\r
867 FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
\r
868 if (ctor.isVarArgsMethod()) {
\r
869 throw Context.reportRuntimeError1
\r
870 ("msg.varargs.ctor", ctorMember.getName());
\r
872 ctor.addAsConstructor(scope, proto);
\r
874 if (!hasPrefix && exclusionList == null)
\r
875 exclusionList = getExclusionList();
\r
876 Method finishInit = null;
\r
877 for (int i=0; i < methods.length; i++) {
\r
878 if (!hasPrefix && methods[i].getDeclaringClass() != clazz)
\r
880 String name = methods[i].getName();
\r
881 if (name.equals("finishInit")) {
\r
882 Class[] parmTypes = methods[i].getParameterTypes();
\r
883 if (parmTypes.length == 3 &&
\r
884 parmTypes[0] == ScriptRuntime.ScriptableClass &&
\r
885 parmTypes[1] == FunctionObject.class &&
\r
886 parmTypes[2] == ScriptRuntime.ScriptableClass &&
\r
887 Modifier.isStatic(methods[i].getModifiers()))
\r
889 finishInit = methods[i];
\r
893 // ignore any compiler generated methods.
\r
894 if (name.indexOf('$') != -1)
\r
896 if (name.equals(ctorName))
\r
898 String prefix = null;
\r
900 if (name.startsWith(genericPrefix)) {
\r
901 prefix = genericPrefix;
\r
902 } else if (name.startsWith(functionPrefix)) {
\r
903 prefix = functionPrefix;
\r
904 } else if (name.startsWith(staticFunctionPrefix)) {
\r
905 prefix = staticFunctionPrefix;
\r
906 if (!Modifier.isStatic(methods[i].getModifiers())) {
\r
907 throw new ClassDefinitionException(
\r
908 "jsStaticFunction must be used with static method.");
\r
910 } else if (name.startsWith(propertyPrefix)) {
\r
911 prefix = propertyPrefix;
\r
912 } else if (name.startsWith(getterPrefix)) {
\r
913 prefix = getterPrefix;
\r
914 } else if (name.startsWith(setterPrefix)) {
\r
915 prefix = setterPrefix;
\r
919 name = name.substring(prefix.length());
\r
920 } else if (exclusionList.get(name) != null)
\r
922 if (methods[i] == ctorMember) {
\r
925 if (prefix != null && prefix.equals(setterPrefix))
\r
926 continue; // deal with set when we see get
\r
927 if (prefix != null && prefix.equals(getterPrefix)) {
\r
928 if (!(proto instanceof ScriptableObject)) {
\r
929 throw PropertyException.withMessage2
\r
930 ("msg.extend.scriptable", proto.getClass().toString(), name);
\r
932 Method[] setter = FunctionObject.findMethods(
\r
934 setterPrefix + name);
\r
935 if (setter != null && setter.length != 1) {
\r
936 throw PropertyException.withMessage2
\r
937 ("msg.no.overload", name, clazz.getName());
\r
939 int attr = ScriptableObject.PERMANENT |
\r
940 ScriptableObject.DONTENUM |
\r
941 (setter != null ? 0
\r
942 : ScriptableObject.READONLY);
\r
943 Method m = setter == null ? null : setter[0];
\r
944 ((ScriptableObject) proto).defineProperty(name, null,
\r
949 if ((name.startsWith("get") || name.startsWith("set")) &&
\r
950 name.length() > 3 &&
\r
951 !(hasPrefix && (prefix.equals(functionPrefix) ||
\r
952 prefix.equals(staticFunctionPrefix))))
\r
954 if (!(proto instanceof ScriptableObject)) {
\r
955 throw PropertyException.withMessage2
\r
956 ("msg.extend.scriptable",
\r
957 proto.getClass().toString(), name);
\r
959 if (name.startsWith("set"))
\r
960 continue; // deal with set when we see get
\r
961 StringBuffer buf = new StringBuffer();
\r
962 char c = name.charAt(3);
\r
963 buf.append(Character.toLowerCase(c));
\r
964 if (name.length() > 4)
\r
965 buf.append(name.substring(4));
\r
966 String propertyName = buf.toString();
\r
967 buf.setCharAt(0, c);
\r
968 buf.insert(0, "set");
\r
969 String setterName = buf.toString();
\r
970 Method[] setter = FunctionObject.findMethods(
\r
972 hasPrefix ? genericPrefix + setterName
\r
974 if (setter != null && setter.length != 1) {
\r
975 throw PropertyException.withMessage2
\r
976 ("msg.no.overload", name, clazz.getName());
\r
978 if (setter == null && hasPrefix)
\r
979 setter = FunctionObject.findMethods(
\r
981 propertyPrefix + setterName);
\r
982 int attr = ScriptableObject.PERMANENT |
\r
983 ScriptableObject.DONTENUM |
\r
984 (setter != null ? 0
\r
985 : ScriptableObject.READONLY);
\r
986 Method m = setter == null ? null : setter[0];
\r
987 ((ScriptableObject) proto).defineProperty(propertyName, null,
\r
992 FunctionObject f = new FunctionObject(name, methods[i], proto);
\r
993 if (f.isVarArgsConstructor()) {
\r
994 throw Context.reportRuntimeError1
\r
995 ("msg.varargs.fun", ctorMember.getName());
\r
997 Scriptable dest = prefix == staticFunctionPrefix
\r
1000 defineProperty(dest, name, f, DONTENUM);
\r
1003 f.addPropertyAttribute(READONLY);
\r
1007 if (finishInit != null) {
\r
1008 // call user code to complete the initialization
\r
1009 Object[] finishArgs = { scope, ctor, proto };
\r
1010 finishInit.invoke(null, finishArgs);
\r
1014 ctor.sealObject();
\r
1015 ctor.addPropertyAttribute(READONLY);
\r
1016 if (proto instanceof ScriptableObject) {
\r
1017 ((ScriptableObject) proto).sealObject();
\r
1018 ((ScriptableObject) proto).addPropertyAttribute(READONLY);
\r
1024 * Define a JavaScript property.
\r
1026 * Creates the property with an initial value and sets its attributes.
\r
1028 * @param propertyName the name of the property to define.
\r
1029 * @param value the initial value of the property
\r
1030 * @param attributes the attributes of the JavaScript property
\r
1031 * @see org.mozilla.javascript.Scriptable#put
\r
1033 public void defineProperty(String propertyName, Object value,
\r
1036 put(propertyName, this, value);
\r
1038 setAttributes(propertyName, this, attributes);
\r
1040 catch (PropertyException e) {
\r
1041 throw new RuntimeException("Cannot create property");
\r
1046 * Utility method to add properties to arbitrary Scriptable object.
\r
1047 * If destination is instance of ScriptableObject, calls
\r
1048 * defineProperty there, otherwise calls put in destination
\r
1049 * ignoring attributes
\r
1051 public static void defineProperty(Scriptable destination,
\r
1052 String propertyName, Object value,
\r
1055 if (destination instanceof ScriptableObject) {
\r
1056 ScriptableObject obj = (ScriptableObject)destination;
\r
1057 obj.defineProperty(propertyName, value, attributes);
\r
1060 destination.put(propertyName, destination, value);
\r
1065 * Define a JavaScript property with getter and setter side effects.
\r
1067 * If the setter is not found, the attribute READONLY is added to
\r
1068 * the given attributes. <p>
\r
1070 * The getter must be a method with zero parameters, and the setter, if
\r
1071 * found, must be a method with one parameter.<p>
\r
1073 * @param propertyName the name of the property to define. This name
\r
1074 * also affects the name of the setter and getter
\r
1075 * to search for. If the propertyId is "foo", then
\r
1076 * <code>clazz</code> will be searched for "getFoo"
\r
1077 * and "setFoo" methods.
\r
1078 * @param clazz the Java class to search for the getter and setter
\r
1079 * @param attributes the attributes of the JavaScript property
\r
1080 * @exception PropertyException if multiple methods
\r
1081 * are found for the getter or setter, or if the getter
\r
1082 * or setter do not conform to the forms described in
\r
1083 * <code>defineProperty(String, Object, Method, Method,
\r
1085 * @see org.mozilla.javascript.Scriptable#put
\r
1087 public void defineProperty(String propertyName, Class clazz,
\r
1089 throws PropertyException
\r
1091 StringBuffer buf = new StringBuffer(propertyName);
\r
1092 buf.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
\r
1093 String s = buf.toString();
\r
1094 Method[] getter = FunctionObject.findMethods(clazz, "get" + s);
\r
1095 Method[] setter = FunctionObject.findMethods(clazz, "set" + s);
\r
1096 if (setter == null)
\r
1097 attributes |= ScriptableObject.READONLY;
\r
1098 if (getter.length != 1 || (setter != null && setter.length != 1)) {
\r
1099 throw PropertyException.withMessage2
\r
1100 ("msg.no.overload", propertyName, clazz.getName());
\r
1102 defineProperty(propertyName, null, getter[0],
\r
1103 setter == null ? null : setter[0], attributes);
\r
1107 * Define a JavaScript property.
\r
1109 * Use this method only if you wish to define getters and setters for
\r
1110 * a given property in a ScriptableObject. To create a property without
\r
1111 * special getter or setter side effects, use
\r
1112 * <code>defineProperty(String,int)</code>.
\r
1114 * If <code>setter</code> is null, the attribute READONLY is added to
\r
1115 * the given attributes.<p>
\r
1117 * Several forms of getters or setters are allowed. In all cases the
\r
1118 * type of the value parameter can be any one of the following types:
\r
1119 * Object, String, boolean, Scriptable, byte, short, int, long, float,
\r
1120 * or double. The runtime will perform appropriate conversions based
\r
1121 * upon the type of the parameter (see description in FunctionObject).
\r
1122 * The first forms are nonstatic methods of the class referred to
\r
1125 * Object getFoo();
\r
1126 * void setFoo(SomeType value);</pre>
\r
1127 * Next are static methods that may be of any class; the object whose
\r
1128 * property is being accessed is passed in as an extra argument:
\r
1130 * static Object getFoo(ScriptableObject obj);
\r
1131 * static void setFoo(ScriptableObject obj, SomeType value);</pre>
\r
1132 * Finally, it is possible to delegate to another object entirely using
\r
1133 * the <code>delegateTo</code> parameter. In this case the methods are
\r
1134 * nonstatic methods of the class delegated to, and the object whose
\r
1135 * property is being accessed is passed in as an extra argument:
\r
1137 * Object getFoo(ScriptableObject obj);
\r
1138 * void setFoo(ScriptableObject obj, SomeType value);</pre>
\r
1140 * @param propertyName the name of the property to define.
\r
1141 * @param delegateTo an object to call the getter and setter methods on,
\r
1142 * or null, depending on the form used above.
\r
1143 * @param getter the method to invoke to get the value of the property
\r
1144 * @param setter the method to invoke to set the value of the property
\r
1145 * @param attributes the attributes of the JavaScript property
\r
1146 * @exception PropertyException if the getter or setter
\r
1147 * do not conform to the forms specified above
\r
1149 public void defineProperty(String propertyName, Object delegateTo,
\r
1150 Method getter, Method setter, int attributes)
\r
1151 throws PropertyException
\r
1153 int flags = Slot.HAS_GETTER;
\r
1154 if (delegateTo == null && (Modifier.isStatic(getter.getModifiers())))
\r
1155 delegateTo = HAS_STATIC_ACCESSORS;
\r
1156 Class[] parmTypes = getter.getParameterTypes();
\r
1157 if (parmTypes.length != 0) {
\r
1158 if (parmTypes.length != 1 ||
\r
1159 parmTypes[0] != ScriptableObject.class)
\r
1161 throw PropertyException.withMessage1
\r
1162 ("msg.bad.getter.parms", getter.toString());
\r
1164 } else if (delegateTo != null) {
\r
1165 throw PropertyException.withMessage1
\r
1166 ("msg.obj.getter.parms", getter.toString());
\r
1168 if (setter != null) {
\r
1169 flags |= Slot.HAS_SETTER;
\r
1170 if ((delegateTo == HAS_STATIC_ACCESSORS) !=
\r
1171 (Modifier.isStatic(setter.getModifiers())))
\r
1173 throw PropertyException.withMessage0("msg.getter.static");
\r
1175 parmTypes = setter.getParameterTypes();
\r
1176 if (parmTypes.length == 2) {
\r
1177 if (parmTypes[0] != ScriptableObject.class) {
\r
1178 throw PropertyException.withMessage0("msg.setter2.parms");
\r
1180 if (delegateTo == null) {
\r
1181 throw PropertyException.withMessage1
\r
1182 ("msg.setter1.parms", setter.toString());
\r
1184 } else if (parmTypes.length == 1) {
\r
1185 if (delegateTo != null) {
\r
1186 throw PropertyException.withMessage1
\r
1187 ("msg.setter2.expected", setter.toString());
\r
1190 throw PropertyException.withMessage0("msg.setter.parms");
\r
1193 GetterSlot slot = (GetterSlot)getSlotToSet(propertyName,
\r
1194 propertyName.hashCode(),
\r
1196 slot.delegateTo = delegateTo;
\r
1197 slot.getter = getter;
\r
1198 slot.setter = setter;
\r
1199 slot.setterReturnsValue = setter != null && setter.getReturnType() != Void.TYPE;
\r
1200 slot.value = null;
\r
1201 slot.attributes = (short) attributes;
\r
1202 slot.flags = (byte)flags;
\r
1206 * Search for names in a class, adding the resulting methods
\r
1209 * <p> Uses reflection to find the methods of the given names. Then
\r
1210 * FunctionObjects are constructed from the methods found, and
\r
1211 * are added to this object as properties with the given names.
\r
1213 * @param names the names of the Methods to add as function properties
\r
1214 * @param clazz the class to search for the Methods
\r
1215 * @param attributes the attributes of the new properties
\r
1216 * @exception PropertyException if any of the names
\r
1217 * has no corresponding method or more than one corresponding
\r
1218 * method in the class
\r
1219 * @see org.mozilla.javascript.FunctionObject
\r
1221 public void defineFunctionProperties(String[] names, Class clazz,
\r
1223 throws PropertyException
\r
1225 for (int i=0; i < names.length; i++) {
\r
1226 String name = names[i];
\r
1227 Method[] m = FunctionObject.findMethods(clazz, name);
\r
1229 throw PropertyException.withMessage2
\r
1230 ("msg.method.not.found", name, clazz.getName());
\r
1232 if (m.length > 1) {
\r
1233 throw PropertyException.withMessage2
\r
1234 ("msg.no.overload", name, clazz.getName());
\r
1236 FunctionObject f = new FunctionObject(name, m[0], this);
\r
1237 defineProperty(name, f, attributes);
\r
1242 * Get the Object.prototype property.
\r
1243 * See ECMA 15.2.4.
\r
1245 public static Scriptable getObjectPrototype(Scriptable scope) {
\r
1246 return getClassPrototype(scope, "Object");
\r
1250 * Get the Function.prototype property.
\r
1251 * See ECMA 15.3.4.
\r
1253 public static Scriptable getFunctionPrototype(Scriptable scope) {
\r
1254 return getClassPrototype(scope, "Function");
\r
1258 * Get the prototype for the named class.
\r
1260 * For example, <code>getClassPrototype(s, "Date")</code> will first
\r
1261 * walk up the parent chain to find the outermost scope, then will
\r
1262 * search that scope for the Date constructor, and then will
\r
1263 * return Date.prototype. If any of the lookups fail, or
\r
1264 * the prototype is not a JavaScript object, then null will
\r
1267 * @param scope an object in the scope chain
\r
1268 * @param className the name of the constructor
\r
1269 * @return the prototype for the named class, or null if it
\r
1270 * cannot be found.
\r
1272 public static Scriptable getClassPrototype(Scriptable scope,
\r
1275 scope = getTopLevelScope(scope);
\r
1276 Object ctor = ScriptRuntime.getTopLevelProp(scope, className);
\r
1277 if (ctor == NOT_FOUND || !(ctor instanceof Scriptable))
\r
1279 Scriptable ctorObj = (Scriptable) ctor;
\r
1280 if (!ctorObj.has("prototype", ctorObj))
\r
1282 Object proto = ctorObj.get("prototype", ctorObj);
\r
1283 if (!(proto instanceof Scriptable))
\r
1285 return (Scriptable) proto;
\r
1289 * Get the global scope.
\r
1291 * <p>Walks the parent scope chain to find an object with a null
\r
1292 * parent scope (the global object).
\r
1294 * @param obj a JavaScript object
\r
1295 * @return the corresponding global scope
\r
1297 public static Scriptable getTopLevelScope(Scriptable obj) {
\r
1298 Scriptable next = obj;
\r
1301 next = obj.getParentScope();
\r
1302 } while (next != null);
\r
1307 * Seal this object.
\r
1309 * A sealed object may not have properties added or removed. Once
\r
1310 * an object is sealed it may not be unsealed.
\r
1314 public void sealObject() {
\r
1319 * Return true if this object is sealed.
\r
1321 * It is an error to attempt to add or remove properties to
\r
1322 * a sealed object.
\r
1324 * @return true if sealed, false otherwise.
\r
1327 public boolean isSealed() {
\r
1328 return count == -1;
\r
1332 * Gets a named property from an object or any object in its prototype chain.
\r
1334 * Searches the prototype chain for a property named <code>name</code>.
\r
1336 * @param obj a JavaScript object
\r
1337 * @param name a property name
\r
1338 * @return the value of a property with name <code>name</code> found in
\r
1339 * <code>obj</code> or any object in its prototype chain, or
\r
1340 * <code>Scriptable.NOT_FOUND</code> if not found
\r
1343 public static Object getProperty(Scriptable obj, String name) {
\r
1344 Scriptable start = obj;
\r
1347 result = obj.get(name, start);
\r
1348 if (result != Scriptable.NOT_FOUND)
\r
1350 obj = obj.getPrototype();
\r
1351 } while (obj != null);
\r
1356 * Gets an indexed property from an object or any object in its prototype chain.
\r
1358 * Searches the prototype chain for a property with integral index
\r
1359 * <code>index</code>. Note that if you wish to look for properties with numerical
\r
1360 * but non-integral indicies, you should use getProperty(Scriptable,String) with
\r
1361 * the string value of the index.
\r
1363 * @param obj a JavaScript object
\r
1364 * @param index an integral index
\r
1365 * @return the value of a property with index <code>index</code> found in
\r
1366 * <code>obj</code> or any object in its prototype chain, or
\r
1367 * <code>Scriptable.NOT_FOUND</code> if not found
\r
1370 public static Object getProperty(Scriptable obj, int index) {
\r
1371 Scriptable start = obj;
\r
1374 result = obj.get(index, start);
\r
1375 if (result != Scriptable.NOT_FOUND)
\r
1377 obj = obj.getPrototype();
\r
1378 } while (obj != null);
\r
1383 * Returns whether a named property is defined in an object or any object
\r
1384 * in its prototype chain.
\r
1386 * Searches the prototype chain for a property named <code>name</code>.
\r
1388 * @param obj a JavaScript object
\r
1389 * @param name a property name
\r
1390 * @return the true if property was found
\r
1393 public static boolean hasProperty(Scriptable obj, String name) {
\r
1394 Scriptable start = obj;
\r
1396 if (obj.has(name, start))
\r
1398 obj = obj.getPrototype();
\r
1399 } while (obj != null);
\r
1404 * Returns whether an indexed property is defined in an object or any object
\r
1405 * in its prototype chain.
\r
1407 * Searches the prototype chain for a property with index <code>index</code>.
\r
1409 * @param obj a JavaScript object
\r
1410 * @param index a property index
\r
1411 * @return the true if property was found
\r
1414 public static boolean hasProperty(Scriptable obj, int index) {
\r
1415 Scriptable start = obj;
\r
1417 if (obj.has(index, start))
\r
1419 obj = obj.getPrototype();
\r
1420 } while (obj != null);
\r
1425 * Puts a named property in an object or in an object in its prototype chain.
\r
1427 * Seaches for the named property in the prototype chain. If it is found,
\r
1428 * the value of the property is changed. If it is not found, a new
\r
1429 * property is added in <code>obj</code>.
\r
1430 * @param obj a JavaScript object
\r
1431 * @param name a property name
\r
1432 * @param value any JavaScript value accepted by Scriptable.put
\r
1435 public static void putProperty(Scriptable obj, String name, Object value) {
\r
1436 Scriptable base = getBase(obj, name);
\r
1439 base.put(name, obj, value);
\r
1443 * Puts an indexed property in an object or in an object in its prototype chain.
\r
1445 * Seaches for the indexed property in the prototype chain. If it is found,
\r
1446 * the value of the property is changed. If it is not found, a new
\r
1447 * property is added in <code>obj</code>.
\r
1448 * @param obj a JavaScript object
\r
1449 * @param index a property index
\r
1450 * @param value any JavaScript value accepted by Scriptable.put
\r
1453 public static void putProperty(Scriptable obj, int index, Object value) {
\r
1454 Scriptable base = getBase(obj, index);
\r
1457 base.put(index, obj, value);
\r
1461 * Removes the property from an object or its prototype chain.
\r
1463 * Searches for a property with <code>name</code> in obj or
\r
1464 * its prototype chain. If it is found, the object's delete
\r
1465 * method is called.
\r
1466 * @param obj a JavaScript object
\r
1467 * @param name a property name
\r
1468 * @return true if the property doesn't exist or was successfully removed
\r
1471 public static boolean deleteProperty(Scriptable obj, String name) {
\r
1472 Scriptable base = getBase(obj, name);
\r
1475 base.delete(name);
\r
1476 return base.get(name, obj) == NOT_FOUND;
\r
1480 * Removes the property from an object or its prototype chain.
\r
1482 * Searches for a property with <code>index</code> in obj or
\r
1483 * its prototype chain. If it is found, the object's delete
\r
1484 * method is called.
\r
1485 * @param obj a JavaScript object
\r
1486 * @param index a property index
\r
1487 * @return true if the property doesn't exist or was successfully removed
\r
1490 public static boolean deleteProperty(Scriptable obj, int index) {
\r
1491 Scriptable base = getBase(obj, index);
\r
1494 base.delete(index);
\r
1495 return base.get(index, obj) == NOT_FOUND;
\r
1499 * Returns an array of all ids from an object and its prototypes.
\r
1501 * @param obj a JavaScript object
\r
1502 * @return an array of all ids from all object in the prototype chain.
\r
1503 * If a given id occurs multiple times in the prototype chain,
\r
1504 * it will occur only once in this list.
\r
1507 public static Object[] getPropertyIds(Scriptable obj) {
\r
1508 Hashtable h = new Hashtable(); // JDK1.2: use HashSet
\r
1509 while (obj != null) {
\r
1510 Object[] ids = obj.getIds();
\r
1511 for (int i=0; i < ids.length; i++) {
\r
1512 h.put(ids[i], ids[i]);
\r
1514 obj = (Scriptable)obj.getPrototype();
\r
1516 Object[] result = new Object[h.size()];
\r
1517 java.util.Enumeration e = h.elements();
\r
1519 while (e.hasMoreElements()) {
\r
1520 result[n++] = e.nextElement();
\r
1526 * Call a method of an object.
\r
1528 * @param obj the JavaScript object
\r
1529 * @param methodName the name of the function property
\r
1530 * @param args the arguments for the call
\r
1531 * @exception JavaScriptException thrown if there were errors in the call
\r
1533 public static Object callMethod(Scriptable obj, String methodName,
\r
1535 throws JavaScriptException
\r
1537 Context cx = Context.enter();
\r
1539 Object fun = getProperty(obj, methodName);
\r
1540 if (fun == NOT_FOUND)
\r
1541 fun = Undefined.instance;
\r
1542 return ScriptRuntime.call(cx, fun, obj, args, getTopLevelScope(obj));
\r
1548 private static Scriptable getBase(Scriptable obj, String s) {
\r
1549 Scriptable m = obj;
\r
1550 while (m != null) {
\r
1551 if (m.has(s, obj))
\r
1553 m = m.getPrototype();
\r
1558 private static Scriptable getBase(Scriptable obj, int index) {
\r
1559 Scriptable m = obj;
\r
1560 while (m != null) {
\r
1561 if (m.has(index, obj))
\r
1563 m = m.getPrototype();
\r
1569 * Adds a property attribute to all properties.
\r
1571 synchronized void addPropertyAttribute(int attribute) {
\r
1572 if (slots == null)
\r
1574 for (int i=0; i < slots.length; i++) {
\r
1575 Slot slot = slots[i];
\r
1576 if (slot == null || slot == REMOVED)
\r
1578 if ((slot.flags & slot.HAS_SETTER) != 0 && attribute == READONLY)
\r
1580 slot.attributes |= attribute;
\r
1584 private Slot getSlot(String id, int index, boolean shouldDelete) {
\r
1585 Slot[] slots = this.slots;
\r
1586 if (slots == null)
\r
1588 int start = (index & 0x7fffffff) % slots.length;
\r
1591 Slot slot = slots[i];
\r
1594 if (slot != REMOVED && slot.intKey == index &&
\r
1595 (slot.stringKey == id || (id != null &&
\r
1596 id.equals(slot.stringKey))))
\r
1598 if (shouldDelete) {
\r
1599 if ((slot.attributes & PERMANENT) == 0) {
\r
1600 // Mark the slot as removed to handle a case when
\r
1601 // another thread manages to put just removed slot
\r
1602 // into lastAccess cache.
\r
1603 slot.wasDeleted = (byte)1;
\r
1604 slots[i] = REMOVED;
\r
1606 if (slot == lastAccess)
\r
1607 lastAccess = REMOVED;
\r
1612 if (++i == slots.length)
\r
1614 } while (i != start);
\r
1618 private Slot getSlotToSet(String id, int index, boolean getterSlot) {
\r
1619 if (slots == null)
\r
1620 slots = new Slot[5];
\r
1621 int start = (index & 0x7fffffff) % slots.length;
\r
1622 boolean sawRemoved = false;
\r
1625 Slot slot = slots[i];
\r
1626 if (slot == null) {
\r
1627 return addSlot(id, index, getterSlot);
\r
1629 if (slot == REMOVED) {
\r
1630 sawRemoved = true;
\r
1631 } else if (slot.intKey == index &&
\r
1632 (slot.stringKey == id ||
\r
1633 (id != null && id.equals(slot.stringKey))))
\r
1637 if (++i == slots.length)
\r
1639 } while (i != start);
\r
1641 // Table could be full, but with some REMOVED elements.
\r
1642 // Call to addSlot will use a slot currently taken by
\r
1644 return addSlot(id, index, getterSlot);
\r
1646 throw new RuntimeException("Hashtable internal error");
\r
1650 * Add a new slot to the hash table.
\r
1652 * This method must be synchronized since it is altering the hash
\r
1653 * table itself. Note that we search again for the slot to set
\r
1654 * since another thread could have added the given property or
\r
1655 * caused the table to grow while this thread was searching.
\r
1657 private synchronized Slot addSlot(String id, int index, boolean getterSlot)
\r
1660 throw Context.reportRuntimeError0("msg.add.sealed");
\r
1661 int start = (index & 0x7fffffff) % slots.length;
\r
1664 Slot slot = slots[i];
\r
1665 if (slot == null || slot == REMOVED) {
\r
1666 if ((4 * (count+1)) > (3 * slots.length)) {
\r
1668 return getSlotToSet(id, index, getterSlot);
\r
1670 slot = getterSlot ? new GetterSlot() : new Slot();
\r
1671 slot.stringKey = id;
\r
1672 slot.intKey = index;
\r
1677 if (slot.intKey == index &&
\r
1678 (slot.stringKey == id || (id != null &&
\r
1679 id.equals(slot.stringKey))))
\r
1683 if (++i == slots.length)
\r
1685 } while (i != start);
\r
1686 throw new RuntimeException("Hashtable internal error");
\r
1690 * Remove a slot from the hash table.
\r
1692 * This method must be synchronized since it is altering the hash
\r
1693 * table itself. We might be able to optimize this more, but
\r
1694 * deletes are not common.
\r
1696 private synchronized void removeSlot(String name, int index) {
\r
1698 throw Context.reportRuntimeError0("msg.remove.sealed");
\r
1699 getSlot(name, index, true);
\r
1703 * Grow the hash table to accommodate new entries.
\r
1705 * Note that by assigning the new array back at the end we
\r
1706 * can continue reading the array from other threads.
\r
1708 private synchronized void grow() {
\r
1709 Slot[] newSlots = new Slot[slots.length*2 + 1];
\r
1710 for (int j=slots.length-1; j >= 0 ; j--) {
\r
1711 Slot slot = slots[j];
\r
1712 if (slot == null || slot == REMOVED)
\r
1714 int k = (slot.intKey & 0x7fffffff) % newSlots.length;
\r
1715 while (newSlots[k] != null)
\r
1716 if (++k == newSlots.length)
\r
1718 // The end of the "synchronized" statement will cause the memory
\r
1719 // writes to be propagated on a multiprocessor machine. We want
\r
1720 // to make sure that the new table is prepared to be read.
\r
1721 // XXX causes the 'this' pointer to be null in calling stack frames
\r
1723 //synchronized (slot) { }
\r
1724 newSlots[k] = slot;
\r
1729 private static Hashtable getExclusionList() {
\r
1730 if (exclusionList != null)
\r
1731 return exclusionList;
\r
1732 Hashtable result = new Hashtable(17);
\r
1733 Method[] methods = ScriptRuntime.FunctionClass.getMethods();
\r
1734 for (int i=0; i < methods.length; i++) {
\r
1735 result.put(methods[i].getName(), Boolean.TRUE);
\r
1737 exclusionList = result;
\r
1741 Object[] getIds(boolean getAll) {
\r
1743 Object[] a = ScriptRuntime.emptyArgs;
\r
1747 for (int i=0; i < s.length; i++) {
\r
1749 if (slot == null || slot == REMOVED)
\r
1751 if (getAll || (slot.attributes & DONTENUM) == 0) {
\r
1753 a = new Object[s.length - i];
\r
1754 a[c++] = slot.stringKey != null
\r
1755 ? (Object) slot.stringKey
\r
1756 : new Integer(slot.intKey);
\r
1759 if (c == a.length)
\r
1761 Object[] result = new Object[c];
\r
1762 System.arraycopy(a, 0, result, 0, c);
\r
1768 * The prototype of this object.
\r
1770 protected Scriptable prototype;
\r
1773 * The parent scope of this object.
\r
1775 protected Scriptable parent;
\r
1777 private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;
\r
1778 private static final Slot REMOVED = new Slot();
\r
1779 private static Hashtable exclusionList = null;
\r
1781 private Slot[] slots;
\r
1782 private int count;
\r
1784 // cache; may be removed for smaller memory footprint
\r
1785 private Slot lastAccess = REMOVED;
\r
1787 private static class Slot {
\r
1788 static final int HAS_GETTER = 0x01;
\r
1789 static final int HAS_SETTER = 0x02;
\r
1799 private static class GetterSlot extends Slot {
\r
1800 Object delegateTo; // OPT: merge with "value"
\r
1803 boolean setterReturnsValue;
\r
1806 private static final Class ContextClass = Context.class;
\r