0001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
0002: *
0003: * ***** BEGIN LICENSE BLOCK *****
0004: * Version: MPL 1.1/GPL 2.0
0005: *
0006: * The contents of this file are subject to the Mozilla Public License Version
0007: * 1.1 (the "License"); you may not use this file except in compliance with
0008: * the License. You may obtain a copy of the License at
0009: * http://www.mozilla.org/MPL/
0010: *
0011: * Software distributed under the License is distributed on an "AS IS" basis,
0012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0013: * for the specific language governing rights and limitations under the
0014: * License.
0015: *
0016: * The Original Code is Rhino code, released
0017: * May 6, 1999.
0018: *
0019: * The Initial Developer of the Original Code is
0020: * Netscape Communications Corporation.
0021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
0022: * the Initial Developer. All Rights Reserved.
0023: *
0024: * Contributor(s):
0025: * Norris Boyd
0026: * Igor Bukanov
0027: * Bob Jervis
0028: * Roger Lawrence
0029: * Steve Weiss
0030: *
0031: * Alternatively, the contents of this file may be used under the terms of
0032: * the GNU General Public License Version 2 or later (the "GPL"), in which
0033: * case the provisions of the GPL are applicable instead of those above. If
0034: * you wish to allow use of your version of this file only under the terms of
0035: * the GPL and not to allow others to use your version of this file under the
0036: * MPL, indicate your decision by deleting the provisions above and replacing
0037: * them with the notice and other provisions required by the GPL. If you do
0038: * not delete the provisions above, a recipient may use your version of this
0039: * file under either the MPL or the GPL.
0040: *
0041: * ***** END LICENSE BLOCK ***** */
0042:
0043: // API class
0044: package org.mozilla.javascript;
0045:
0046: import java.lang.reflect.*;
0047: import java.util.Hashtable;
0048: import java.io.*;
0049: import org.mozilla.javascript.debug.DebuggableObject;
0050:
0051: /**
0052: * This is the default implementation of the Scriptable interface. This
0053: * class provides convenient default behavior that makes it easier to
0054: * define host objects.
0055: * <p>
0056: * Various properties and methods of JavaScript objects can be conveniently
0057: * defined using methods of ScriptableObject.
0058: * <p>
0059: * Classes extending ScriptableObject must define the getClassName method.
0060: *
0061: * @see org.mozilla.javascript.Scriptable
0062: * @author Norris Boyd
0063: */
0064:
0065: public abstract class ScriptableObject implements Scriptable,
0066: Serializable, DebuggableObject, ConstProperties {
0067:
0068: /**
0069: * The empty property attribute.
0070: *
0071: * Used by getAttributes() and setAttributes().
0072: *
0073: * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
0074: * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
0075: */
0076: public static final int EMPTY = 0x00;
0077:
0078: /**
0079: * Property attribute indicating assignment to this property is ignored.
0080: *
0081: * @see org.mozilla.javascript.ScriptableObject
0082: * #put(String, Scriptable, Object)
0083: * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
0084: * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
0085: */
0086: public static final int READONLY = 0x01;
0087:
0088: /**
0089: * Property attribute indicating property is not enumerated.
0090: *
0091: * Only enumerated properties will be returned by getIds().
0092: *
0093: * @see org.mozilla.javascript.ScriptableObject#getIds()
0094: * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
0095: * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
0096: */
0097: public static final int DONTENUM = 0x02;
0098:
0099: /**
0100: * Property attribute indicating property cannot be deleted.
0101: *
0102: * @see org.mozilla.javascript.ScriptableObject#delete(String)
0103: * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
0104: * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
0105: */
0106: public static final int PERMANENT = 0x04;
0107:
0108: /**
0109: * Property attribute indicating that this is a const property that has not
0110: * been assigned yet. The first 'const' assignment to the property will
0111: * clear this bit.
0112: */
0113: public static final int UNINITIALIZED_CONST = 0x08;
0114:
0115: public static final int CONST = PERMANENT | READONLY
0116: | UNINITIALIZED_CONST;
0117: /**
0118: * The prototype of this object.
0119: */
0120: private Scriptable prototypeObject;
0121:
0122: /**
0123: * The parent scope of this object.
0124: */
0125: private Scriptable parentScopeObject;
0126:
0127: private static final Slot REMOVED = new Slot(null, 0, READONLY);
0128:
0129: static {
0130: REMOVED.wasDeleted = 1;
0131: }
0132:
0133: private transient Slot[] slots;
0134: // If count >= 0, it gives number of keys or if count < 0,
0135: // it indicates sealed object where ~count gives number of keys
0136: private int count;
0137:
0138: // cache; may be removed for smaller memory footprint
0139: private transient Slot lastAccess = REMOVED;
0140:
0141: // associated values are not serialized
0142: private transient volatile Hashtable associatedValues;
0143:
0144: private static final int SLOT_QUERY = 1;
0145: private static final int SLOT_MODIFY = 2;
0146: private static final int SLOT_REMOVE = 3;
0147: private static final int SLOT_MODIFY_GETTER_SETTER = 4;
0148: private static final int SLOT_MODIFY_CONST = 5;
0149:
0150: private static class Slot implements Serializable {
0151: static final long serialVersionUID = -3539051633409902634L;
0152:
0153: String name; // This can change due to caching
0154: int indexOrHash;
0155: private volatile short attributes;
0156: transient volatile byte wasDeleted;
0157: volatile Object value;
0158: transient volatile Slot next;
0159:
0160: Slot(String name, int indexOrHash, int attributes) {
0161: this .name = name;
0162: this .indexOrHash = indexOrHash;
0163: this .attributes = (short) attributes;
0164: }
0165:
0166: private void readObject(ObjectInputStream in)
0167: throws IOException, ClassNotFoundException {
0168: in.defaultReadObject();
0169: if (name != null) {
0170: indexOrHash = name.hashCode();
0171: }
0172: }
0173:
0174: final int getAttributes() {
0175: return attributes;
0176: }
0177:
0178: final synchronized void setAttributes(int value) {
0179: checkValidAttributes(value);
0180: attributes = (short) value;
0181: }
0182:
0183: final void checkNotReadonly() {
0184: if ((attributes & READONLY) != 0) {
0185: String str = (name != null ? name : Integer
0186: .toString(indexOrHash));
0187: throw Context.reportRuntimeError1(
0188: "msg.modify.readonly", str);
0189: }
0190: }
0191:
0192: }
0193:
0194: private static final class GetterSlot extends Slot {
0195: static final long serialVersionUID = -4900574849788797588L;
0196:
0197: Object getter;
0198: Object setter;
0199:
0200: GetterSlot(String name, int indexOrHash, int attributes) {
0201: super (name, indexOrHash, attributes);
0202: }
0203: }
0204:
0205: static void checkValidAttributes(int attributes) {
0206: final int mask = READONLY | DONTENUM | PERMANENT
0207: | UNINITIALIZED_CONST;
0208: if ((attributes & ~mask) != 0) {
0209: throw new IllegalArgumentException(String
0210: .valueOf(attributes));
0211: }
0212: }
0213:
0214: public ScriptableObject() {
0215: }
0216:
0217: public ScriptableObject(Scriptable scope, Scriptable prototype) {
0218: if (scope == null)
0219: throw new IllegalArgumentException();
0220:
0221: parentScopeObject = scope;
0222: prototypeObject = prototype;
0223: }
0224:
0225: /**
0226: * Return the name of the class.
0227: *
0228: * This is typically the same name as the constructor.
0229: * Classes extending ScriptableObject must implement this abstract
0230: * method.
0231: */
0232: public abstract String getClassName();
0233:
0234: /**
0235: * Returns true if the named property is defined.
0236: *
0237: * @param name the name of the property
0238: * @param start the object in which the lookup began
0239: * @return true if and only if the property was found in the object
0240: */
0241: public boolean has(String name, Scriptable start) {
0242: return null != getSlot(name, 0, SLOT_QUERY);
0243: }
0244:
0245: /**
0246: * Returns true if the property index is defined.
0247: *
0248: * @param index the numeric index for the property
0249: * @param start the object in which the lookup began
0250: * @return true if and only if the property was found in the object
0251: */
0252: public boolean has(int index, Scriptable start) {
0253: return null != getSlot(null, index, SLOT_QUERY);
0254: }
0255:
0256: /**
0257: * Returns the value of the named property or NOT_FOUND.
0258: *
0259: * If the property was created using defineProperty, the
0260: * appropriate getter method is called.
0261: *
0262: * @param name the name of the property
0263: * @param start the object in which the lookup began
0264: * @return the value of the property (may be null), or NOT_FOUND
0265: */
0266: public Object get(String name, Scriptable start) {
0267: return getImpl(name, 0, start);
0268: }
0269:
0270: /**
0271: * Returns the value of the indexed property or NOT_FOUND.
0272: *
0273: * @param index the numeric index for the property
0274: * @param start the object in which the lookup began
0275: * @return the value of the property (may be null), or NOT_FOUND
0276: */
0277: public Object get(int index, Scriptable start) {
0278: return getImpl(null, index, start);
0279: }
0280:
0281: /**
0282: * Sets the value of the named property, creating it if need be.
0283: *
0284: * If the property was created using defineProperty, the
0285: * appropriate setter method is called. <p>
0286: *
0287: * If the property's attributes include READONLY, no action is
0288: * taken.
0289: * This method will actually set the property in the start
0290: * object.
0291: *
0292: * @param name the name of the property
0293: * @param start the object whose property is being set
0294: * @param value value to set the property to
0295: */
0296: public void put(String name, Scriptable start, Object value) {
0297: if (putImpl(name, 0, start, value, EMPTY))
0298: return;
0299:
0300: if (start == this )
0301: throw Kit.codeBug();
0302: start.put(name, start, value);
0303: }
0304:
0305: /**
0306: * Sets the value of the indexed property, creating it if need be.
0307: *
0308: * @param index the numeric index for the property
0309: * @param start the object whose property is being set
0310: * @param value value to set the property to
0311: */
0312: public void put(int index, Scriptable start, Object value) {
0313: if (putImpl(null, index, start, value, EMPTY))
0314: return;
0315:
0316: if (start == this )
0317: throw Kit.codeBug();
0318: start.put(index, start, value);
0319: }
0320:
0321: /**
0322: * Removes a named property from the object.
0323: *
0324: * If the property is not found, or it has the PERMANENT attribute,
0325: * no action is taken.
0326: *
0327: * @param name the name of the property
0328: */
0329: public void delete(String name) {
0330: checkNotSealed(name, 0);
0331: accessSlot(name, 0, SLOT_REMOVE);
0332: }
0333:
0334: /**
0335: * Removes the indexed property from the object.
0336: *
0337: * If the property is not found, or it has the PERMANENT attribute,
0338: * no action is taken.
0339: *
0340: * @param index the numeric index for the property
0341: */
0342: public void delete(int index) {
0343: checkNotSealed(null, index);
0344: accessSlot(null, index, SLOT_REMOVE);
0345: }
0346:
0347: /**
0348: * Sets the value of the named const property, creating it if need be.
0349: *
0350: * If the property was created using defineProperty, the
0351: * appropriate setter method is called. <p>
0352: *
0353: * If the property's attributes include READONLY, no action is
0354: * taken.
0355: * This method will actually set the property in the start
0356: * object.
0357: *
0358: * @param name the name of the property
0359: * @param start the object whose property is being set
0360: * @param value value to set the property to
0361: */
0362: public void putConst(String name, Scriptable start, Object value) {
0363: if (putImpl(name, 0, start, value, READONLY))
0364: return;
0365:
0366: if (start == this )
0367: throw Kit.codeBug();
0368: if (start instanceof ConstProperties)
0369: ((ConstProperties) start).putConst(name, start, value);
0370: else
0371: start.put(name, start, value);
0372: }
0373:
0374: public void defineConst(String name, Scriptable start) {
0375: if (putImpl(name, 0, start, Undefined.instance,
0376: UNINITIALIZED_CONST))
0377: return;
0378:
0379: if (start == this )
0380: throw Kit.codeBug();
0381: if (start instanceof ConstProperties)
0382: ((ConstProperties) start).defineConst(name, start);
0383: }
0384:
0385: /**
0386: * Returns true if the named property is defined as a const on this object.
0387: * @param name
0388: * @return true if the named property is defined as a const, false
0389: * otherwise.
0390: */
0391: public boolean isConst(String name) {
0392: Slot slot = getSlot(name, 0, SLOT_QUERY);
0393: if (slot == null) {
0394: return false;
0395: }
0396: return (slot.getAttributes() & (PERMANENT | READONLY)) == (PERMANENT | READONLY);
0397:
0398: }
0399:
0400: /**
0401: * @deprecated Use {@link #getAttributes(String name)}. The engine always
0402: * ignored the start argument.
0403: */
0404: public final int getAttributes(String name, Scriptable start) {
0405: return getAttributes(name);
0406: }
0407:
0408: /**
0409: * @deprecated Use {@link #getAttributes(int index)}. The engine always
0410: * ignored the start argument.
0411: */
0412: public final int getAttributes(int index, Scriptable start) {
0413: return getAttributes(index);
0414: }
0415:
0416: /**
0417: * @deprecated Use {@link #setAttributes(String name, int attributes)}.
0418: * The engine always ignored the start argument.
0419: */
0420: public final void setAttributes(String name, Scriptable start,
0421: int attributes) {
0422: setAttributes(name, attributes);
0423: }
0424:
0425: /**
0426: * @deprecated Use {@link #setAttributes(int index, int attributes)}.
0427: * The engine always ignored the start argument.
0428: */
0429: public void setAttributes(int index, Scriptable start,
0430: int attributes) {
0431: setAttributes(index, attributes);
0432: }
0433:
0434: /**
0435: * Get the attributes of a named property.
0436: *
0437: * The property is specified by <code>name</code>
0438: * as defined for <code>has</code>.<p>
0439: *
0440: * @param name the identifier for the property
0441: * @return the bitset of attributes
0442: * @exception EvaluatorException if the named property is not found
0443: * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
0444: * @see org.mozilla.javascript.ScriptableObject#READONLY
0445: * @see org.mozilla.javascript.ScriptableObject#DONTENUM
0446: * @see org.mozilla.javascript.ScriptableObject#PERMANENT
0447: * @see org.mozilla.javascript.ScriptableObject#EMPTY
0448: */
0449: public int getAttributes(String name) {
0450: return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes();
0451: }
0452:
0453: /**
0454: * Get the attributes of an indexed property.
0455: *
0456: * @param index the numeric index for the property
0457: * @exception EvaluatorException if the named property is not found
0458: * is not found
0459: * @return the bitset of attributes
0460: * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
0461: * @see org.mozilla.javascript.ScriptableObject#READONLY
0462: * @see org.mozilla.javascript.ScriptableObject#DONTENUM
0463: * @see org.mozilla.javascript.ScriptableObject#PERMANENT
0464: * @see org.mozilla.javascript.ScriptableObject#EMPTY
0465: */
0466: public int getAttributes(int index) {
0467: return findAttributeSlot(null, index, SLOT_QUERY)
0468: .getAttributes();
0469: }
0470:
0471: /**
0472: * Set the attributes of a named property.
0473: *
0474: * The property is specified by <code>name</code>
0475: * as defined for <code>has</code>.<p>
0476: *
0477: * The possible attributes are READONLY, DONTENUM,
0478: * and PERMANENT. Combinations of attributes
0479: * are expressed by the bitwise OR of attributes.
0480: * EMPTY is the state of no attributes set. Any unused
0481: * bits are reserved for future use.
0482: *
0483: * @param name the name of the property
0484: * @param attributes the bitset of attributes
0485: * @exception EvaluatorException if the named property is not found
0486: * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
0487: * @see org.mozilla.javascript.ScriptableObject#READONLY
0488: * @see org.mozilla.javascript.ScriptableObject#DONTENUM
0489: * @see org.mozilla.javascript.ScriptableObject#PERMANENT
0490: * @see org.mozilla.javascript.ScriptableObject#EMPTY
0491: */
0492: public void setAttributes(String name, int attributes) {
0493: checkNotSealed(name, 0);
0494: findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(
0495: attributes);
0496: }
0497:
0498: /**
0499: * Set the attributes of an indexed property.
0500: *
0501: * @param index the numeric index for the property
0502: * @param attributes the bitset of attributes
0503: * @exception EvaluatorException if the named property is not found
0504: * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
0505: * @see org.mozilla.javascript.ScriptableObject#READONLY
0506: * @see org.mozilla.javascript.ScriptableObject#DONTENUM
0507: * @see org.mozilla.javascript.ScriptableObject#PERMANENT
0508: * @see org.mozilla.javascript.ScriptableObject#EMPTY
0509: */
0510: public void setAttributes(int index, int attributes) {
0511: checkNotSealed(null, index);
0512: findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(
0513: attributes);
0514: }
0515:
0516: /**
0517: * XXX: write docs.
0518: */
0519: public void setGetterOrSetter(String name, int index,
0520: Callable getterOrSeter, boolean isSetter) {
0521: if (name != null && index != 0)
0522: throw new IllegalArgumentException(name);
0523:
0524: checkNotSealed(name, index);
0525: GetterSlot gslot = (GetterSlot) getSlot(name, index,
0526: SLOT_MODIFY_GETTER_SETTER);
0527: gslot.checkNotReadonly();
0528: if (isSetter) {
0529: gslot.setter = getterOrSeter;
0530: } else {
0531: gslot.getter = getterOrSeter;
0532: }
0533: gslot.value = Undefined.instance;
0534: }
0535:
0536: /**
0537: * Get the getter or setter for a given property. Used by __lookupGetter__
0538: * and __lookupSetter__.
0539: *
0540: * @param name Name of the object. If nonnull, index must be 0.
0541: * @param index Index of the object. If nonzero, name must be null.
0542: * @param isSetter If true, return the setter, otherwise return the getter.
0543: * @exception IllegalArgumentException if both name and index are nonnull
0544: * and nonzero respectively.
0545: * @return Null if the property does not exist. Otherwise returns either
0546: * the getter or the setter for the property, depending on
0547: * the value of isSetter (may be undefined if unset).
0548: */
0549: public Object getGetterOrSetter(String name, int index,
0550: boolean isSetter) {
0551: if (name != null && index != 0)
0552: throw new IllegalArgumentException(name);
0553: Slot slot = getSlot(name, index, SLOT_QUERY);
0554: if (slot == null)
0555: return null;
0556: if (slot instanceof GetterSlot) {
0557: GetterSlot gslot = (GetterSlot) slot;
0558: Object result = isSetter ? gslot.setter : gslot.getter;
0559: return result != null ? result : Undefined.instance;
0560: } else
0561: return Undefined.instance;
0562: }
0563:
0564: /**
0565: * Returns whether a property is a getter or a setter
0566: * @param name property name
0567: * @param index property index
0568: * @param setter true to check for a setter, false for a getter
0569: * @return whether the property is a getter or a setter
0570: */
0571: protected boolean isGetterOrSetter(String name, int index,
0572: boolean setter) {
0573: Slot slot = getSlot(name, index, SLOT_QUERY);
0574: if (slot instanceof GetterSlot) {
0575: if (setter && ((GetterSlot) slot).setter != null)
0576: return true;
0577: if (!setter && ((GetterSlot) slot).getter != null)
0578: return true;
0579: }
0580: return false;
0581: }
0582:
0583: void addLazilyInitializedValue(String name, int index,
0584: LazilyLoadedCtor init, int attributes) {
0585: if (name != null && index != 0)
0586: throw new IllegalArgumentException(name);
0587: checkNotSealed(name, index);
0588: GetterSlot gslot = (GetterSlot) getSlot(name, index,
0589: SLOT_MODIFY_GETTER_SETTER);
0590: gslot.setAttributes(attributes);
0591: gslot.getter = null;
0592: gslot.setter = null;
0593: gslot.value = init;
0594: }
0595:
0596: /**
0597: * Returns the prototype of the object.
0598: */
0599: public Scriptable getPrototype() {
0600: return prototypeObject;
0601: }
0602:
0603: /**
0604: * Sets the prototype of the object.
0605: */
0606: public void setPrototype(Scriptable m) {
0607: prototypeObject = m;
0608: }
0609:
0610: /**
0611: * Returns the parent (enclosing) scope of the object.
0612: */
0613: public Scriptable getParentScope() {
0614: return parentScopeObject;
0615: }
0616:
0617: /**
0618: * Sets the parent (enclosing) scope of the object.
0619: */
0620: public void setParentScope(Scriptable m) {
0621: parentScopeObject = m;
0622: }
0623:
0624: /**
0625: * Returns an array of ids for the properties of the object.
0626: *
0627: * <p>Any properties with the attribute DONTENUM are not listed. <p>
0628: *
0629: * @return an array of java.lang.Objects with an entry for every
0630: * listed property. Properties accessed via an integer index will
0631: * have a corresponding
0632: * Integer entry in the returned array. Properties accessed by
0633: * a String will have a String entry in the returned array.
0634: */
0635: public Object[] getIds() {
0636: return getIds(false);
0637: }
0638:
0639: /**
0640: * Returns an array of ids for the properties of the object.
0641: *
0642: * <p>All properties, even those with attribute DONTENUM, are listed. <p>
0643: *
0644: * @return an array of java.lang.Objects with an entry for every
0645: * listed property. Properties accessed via an integer index will
0646: * have a corresponding
0647: * Integer entry in the returned array. Properties accessed by
0648: * a String will have a String entry in the returned array.
0649: */
0650: public Object[] getAllIds() {
0651: return getIds(true);
0652: }
0653:
0654: /**
0655: * Implements the [[DefaultValue]] internal method.
0656: *
0657: * <p>Note that the toPrimitive conversion is a no-op for
0658: * every type other than Object, for which [[DefaultValue]]
0659: * is called. See ECMA 9.1.<p>
0660: *
0661: * A <code>hint</code> of null means "no hint".
0662: *
0663: * @param typeHint the type hint
0664: * @return the default value for the object
0665: *
0666: * See ECMA 8.6.2.6.
0667: */
0668: public Object getDefaultValue(Class typeHint) {
0669: return getDefaultValue(this , typeHint);
0670: }
0671:
0672: public static Object getDefaultValue(Scriptable object,
0673: Class typeHint) {
0674: Context cx = null;
0675: for (int i = 0; i < 2; i++) {
0676: boolean tryToString;
0677: if (typeHint == ScriptRuntime.StringClass) {
0678: tryToString = (i == 0);
0679: } else {
0680: tryToString = (i == 1);
0681: }
0682:
0683: String methodName;
0684: Object[] args;
0685: if (tryToString) {
0686: methodName = "toString";
0687: args = ScriptRuntime.emptyArgs;
0688: } else {
0689: methodName = "valueOf";
0690: args = new Object[1];
0691: String hint;
0692: if (typeHint == null) {
0693: hint = "undefined";
0694: } else if (typeHint == ScriptRuntime.StringClass) {
0695: hint = "string";
0696: } else if (typeHint == ScriptRuntime.ScriptableClass) {
0697: hint = "object";
0698: } else if (typeHint == ScriptRuntime.FunctionClass) {
0699: hint = "function";
0700: } else if (typeHint == ScriptRuntime.BooleanClass
0701: || typeHint == Boolean.TYPE) {
0702: hint = "boolean";
0703: } else if (typeHint == ScriptRuntime.NumberClass
0704: || typeHint == ScriptRuntime.ByteClass
0705: || typeHint == Byte.TYPE
0706: || typeHint == ScriptRuntime.ShortClass
0707: || typeHint == Short.TYPE
0708: || typeHint == ScriptRuntime.IntegerClass
0709: || typeHint == Integer.TYPE
0710: || typeHint == ScriptRuntime.FloatClass
0711: || typeHint == Float.TYPE
0712: || typeHint == ScriptRuntime.DoubleClass
0713: || typeHint == Double.TYPE) {
0714: hint = "number";
0715: } else {
0716: throw Context.reportRuntimeError1(
0717: "msg.invalid.type", typeHint.toString());
0718: }
0719: args[0] = hint;
0720: }
0721: Object v = getProperty(object, methodName);
0722: if (!(v instanceof Function))
0723: continue;
0724: Function fun = (Function) v;
0725: if (cx == null)
0726: cx = Context.getContext();
0727: v = fun.call(cx, fun.getParentScope(), object, args);
0728: if (v != null) {
0729: if (!(v instanceof Scriptable)) {
0730: return v;
0731: }
0732: if (typeHint == ScriptRuntime.ScriptableClass
0733: || typeHint == ScriptRuntime.FunctionClass) {
0734: return v;
0735: }
0736: if (tryToString && v instanceof Wrapper) {
0737: // Let a wrapped java.lang.String pass for a primitive
0738: // string.
0739: Object u = ((Wrapper) v).unwrap();
0740: if (u instanceof String)
0741: return u;
0742: }
0743: }
0744: }
0745: // fall through to error
0746: String arg = (typeHint == null) ? "undefined" : typeHint
0747: .getName();
0748: throw ScriptRuntime.typeError1("msg.default.value", arg);
0749: }
0750:
0751: /**
0752: * Implements the instanceof operator.
0753: *
0754: * <p>This operator has been proposed to ECMA.
0755: *
0756: * @param instance The value that appeared on the LHS of the instanceof
0757: * operator
0758: * @return true if "this" appears in value's prototype chain
0759: *
0760: */
0761: public boolean hasInstance(Scriptable instance) {
0762: // Default for JS objects (other than Function) is to do prototype
0763: // chasing. This will be overridden in NativeFunction and non-JS
0764: // objects.
0765:
0766: return ScriptRuntime.jsDelegatesTo(instance, this );
0767: }
0768:
0769: /**
0770: * Emulate the SpiderMonkey (and Firefox) feature of allowing
0771: * custom objects to avoid detection by normal "object detection"
0772: * code patterns. This is used to implement document.all.
0773: * See https://bugzilla.mozilla.org/show_bug.cgi?id=412247.
0774: * This is an analog to JOF_DETECTING from SpiderMonkey; see
0775: * https://bugzilla.mozilla.org/show_bug.cgi?id=248549.
0776: * Other than this special case, embeddings should return false.
0777: * @return true if this object should avoid object detection
0778: * @since 1.7R1
0779: */
0780: public boolean avoidObjectDetection() {
0781: return false;
0782: }
0783:
0784: /**
0785: * Custom <tt>==</tt> operator.
0786: * Must return {@link Scriptable#NOT_FOUND} if this object does not
0787: * have custom equality operator for the given value,
0788: * <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>,
0789: * <tt>Boolean.FALSE</tt> if this object is not equivalent to
0790: * <tt>value</tt>.
0791: * <p>
0792: * The default implementation returns Boolean.TRUE
0793: * if <tt>this == value</tt> or {@link Scriptable#NOT_FOUND} otherwise.
0794: * It indicates that by default custom equality is available only if
0795: * <tt>value</tt> is <tt>this</tt> in which case true is returned.
0796: */
0797: protected Object equivalentValues(Object value) {
0798: return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND;
0799: }
0800:
0801: /**
0802: * Defines JavaScript objects from a Java class that implements Scriptable.
0803: *
0804: * If the given class has a method
0805: * <pre>
0806: * static void init(Context cx, Scriptable scope, boolean sealed);</pre>
0807: *
0808: * or its compatibility form
0809: * <pre>
0810: * static void init(Scriptable scope);</pre>
0811: *
0812: * then it is invoked and no further initialization is done.<p>
0813: *
0814: * However, if no such a method is found, then the class's constructors and
0815: * methods are used to initialize a class in the following manner.<p>
0816: *
0817: * First, the zero-parameter constructor of the class is called to
0818: * create the prototype. If no such constructor exists,
0819: * a {@link EvaluatorException} is thrown. <p>
0820: *
0821: * Next, all methods are scanned for special prefixes that indicate that they
0822: * have special meaning for defining JavaScript objects.
0823: * These special prefixes are
0824: * <ul>
0825: * <li><code>jsFunction_</code> for a JavaScript function
0826: * <li><code>jsStaticFunction_</code> for a JavaScript function that
0827: * is a property of the constructor
0828: * <li><code>jsGet_</code> for a getter of a JavaScript property
0829: * <li><code>jsSet_</code> for a setter of a JavaScript property
0830: * <li><code>jsConstructor</code> for a JavaScript function that
0831: * is the constructor
0832: * </ul><p>
0833: *
0834: * If the method's name begins with "jsFunction_", a JavaScript function
0835: * is created with a name formed from the rest of the Java method name
0836: * following "jsFunction_". So a Java method named "jsFunction_foo" will
0837: * define a JavaScript method "foo". Calling this JavaScript function
0838: * will cause the Java method to be called. The parameters of the method
0839: * must be of number and types as defined by the FunctionObject class.
0840: * The JavaScript function is then added as a property
0841: * of the prototype. <p>
0842: *
0843: * If the method's name begins with "jsStaticFunction_", it is handled
0844: * similarly except that the resulting JavaScript function is added as a
0845: * property of the constructor object. The Java method must be static.
0846: *
0847: * If the method's name begins with "jsGet_" or "jsSet_", the method is
0848: * considered to define a property. Accesses to the defined property
0849: * will result in calls to these getter and setter methods. If no
0850: * setter is defined, the property is defined as READONLY.<p>
0851: *
0852: * If the method's name is "jsConstructor", the method is
0853: * considered to define the body of the constructor. Only one
0854: * method of this name may be defined.
0855: * If no method is found that can serve as constructor, a Java
0856: * constructor will be selected to serve as the JavaScript
0857: * constructor in the following manner. If the class has only one
0858: * Java constructor, that constructor is used to define
0859: * the JavaScript constructor. If the the class has two constructors,
0860: * one must be the zero-argument constructor (otherwise an
0861: * {@link EvaluatorException} would have already been thrown
0862: * when the prototype was to be created). In this case
0863: * the Java constructor with one or more parameters will be used
0864: * to define the JavaScript constructor. If the class has three
0865: * or more constructors, an {@link EvaluatorException}
0866: * will be thrown.<p>
0867: *
0868: * Finally, if there is a method
0869: * <pre>
0870: * static void finishInit(Scriptable scope, FunctionObject constructor,
0871: * Scriptable prototype)</pre>
0872: *
0873: * it will be called to finish any initialization. The <code>scope</code>
0874: * argument will be passed, along with the newly created constructor and
0875: * the newly created prototype.<p>
0876: *
0877: * @param scope The scope in which to define the constructor.
0878: * @param clazz The Java class to use to define the JavaScript objects
0879: * and properties.
0880: * @exception IllegalAccessException if access is not available
0881: * to a reflected class member
0882: * @exception InstantiationException if unable to instantiate
0883: * the named class
0884: * @exception InvocationTargetException if an exception is thrown
0885: * during execution of methods of the named class
0886: * @see org.mozilla.javascript.Function
0887: * @see org.mozilla.javascript.FunctionObject
0888: * @see org.mozilla.javascript.ScriptableObject#READONLY
0889: * @see org.mozilla.javascript.ScriptableObject
0890: * #defineProperty(String, Class, int)
0891: */
0892: public static void defineClass(Scriptable scope, Class clazz)
0893: throws IllegalAccessException, InstantiationException,
0894: InvocationTargetException {
0895: defineClass(scope, clazz, false, false);
0896: }
0897:
0898: /**
0899: * Defines JavaScript objects from a Java class, optionally
0900: * allowing sealing.
0901: *
0902: * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
0903: * except that sealing is allowed. An object that is sealed cannot have
0904: * properties added or removed. Note that sealing is not allowed in
0905: * the current ECMA/ISO language specification, but is likely for
0906: * the next version.
0907: *
0908: * @param scope The scope in which to define the constructor.
0909: * @param clazz The Java class to use to define the JavaScript objects
0910: * and properties. The class must implement Scriptable.
0911: * @param sealed Whether or not to create sealed standard objects that
0912: * cannot be modified.
0913: * @exception IllegalAccessException if access is not available
0914: * to a reflected class member
0915: * @exception InstantiationException if unable to instantiate
0916: * the named class
0917: * @exception InvocationTargetException if an exception is thrown
0918: * during execution of methods of the named class
0919: * @since 1.4R3
0920: */
0921: public static void defineClass(Scriptable scope, Class clazz,
0922: boolean sealed) throws IllegalAccessException,
0923: InstantiationException, InvocationTargetException {
0924: defineClass(scope, clazz, sealed, false);
0925: }
0926:
0927: /**
0928: * Defines JavaScript objects from a Java class, optionally
0929: * allowing sealing and mapping of Java inheritance to JavaScript
0930: * prototype-based inheritance.
0931: *
0932: * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
0933: * except that sealing and inheritance mapping are allowed. An object
0934: * that is sealed cannot have properties added or removed. Note that
0935: * sealing is not allowed in the current ECMA/ISO language specification,
0936: * but is likely for the next version.
0937: *
0938: * @param scope The scope in which to define the constructor.
0939: * @param clazz The Java class to use to define the JavaScript objects
0940: * and properties. The class must implement Scriptable.
0941: * @param sealed Whether or not to create sealed standard objects that
0942: * cannot be modified.
0943: * @param mapInheritance Whether or not to map Java inheritance to
0944: * JavaScript prototype-based inheritance.
0945: * @return the class name for the prototype of the specified class
0946: * @exception IllegalAccessException if access is not available
0947: * to a reflected class member
0948: * @exception InstantiationException if unable to instantiate
0949: * the named class
0950: * @exception InvocationTargetException if an exception is thrown
0951: * during execution of methods of the named class
0952: * @since 1.6R2
0953: */
0954: public static String defineClass(Scriptable scope, Class clazz,
0955: boolean sealed, boolean mapInheritance)
0956: throws IllegalAccessException, InstantiationException,
0957: InvocationTargetException {
0958: BaseFunction ctor = buildClassCtor(scope, clazz, sealed,
0959: mapInheritance);
0960: if (ctor == null)
0961: return null;
0962: String name = ctor.getClassPrototype().getClassName();
0963: defineProperty(scope, name, ctor, ScriptableObject.DONTENUM);
0964: return name;
0965: }
0966:
0967: static BaseFunction buildClassCtor(Scriptable scope, Class clazz,
0968: boolean sealed, boolean mapInheritance)
0969: throws IllegalAccessException, InstantiationException,
0970: InvocationTargetException {
0971: Method[] methods = FunctionObject.getMethodList(clazz);
0972: for (int i = 0; i < methods.length; i++) {
0973: Method method = methods[i];
0974: if (!method.getName().equals("init"))
0975: continue;
0976: Class[] parmTypes = method.getParameterTypes();
0977: if (parmTypes.length == 3
0978: && parmTypes[0] == ScriptRuntime.ContextClass
0979: && parmTypes[1] == ScriptRuntime.ScriptableClass
0980: && parmTypes[2] == Boolean.TYPE
0981: && Modifier.isStatic(method.getModifiers())) {
0982: Object args[] = { Context.getContext(), scope,
0983: sealed ? Boolean.TRUE : Boolean.FALSE };
0984: method.invoke(null, args);
0985: return null;
0986: }
0987: if (parmTypes.length == 1
0988: && parmTypes[0] == ScriptRuntime.ScriptableClass
0989: && Modifier.isStatic(method.getModifiers())) {
0990: Object args[] = { scope };
0991: method.invoke(null, args);
0992: return null;
0993: }
0994:
0995: }
0996:
0997: // If we got here, there isn't an "init" method with the right
0998: // parameter types.
0999:
1000: Constructor[] ctors = clazz.getConstructors();
1001: Constructor protoCtor = null;
1002: for (int i = 0; i < ctors.length; i++) {
1003: if (ctors[i].getParameterTypes().length == 0) {
1004: protoCtor = ctors[i];
1005: break;
1006: }
1007: }
1008: if (protoCtor == null) {
1009: throw Context.reportRuntimeError1("msg.zero.arg.ctor",
1010: clazz.getName());
1011: }
1012:
1013: Scriptable proto = (Scriptable) protoCtor
1014: .newInstance(ScriptRuntime.emptyArgs);
1015: String className = proto.getClassName();
1016:
1017: // Set the prototype's prototype, trying to map Java inheritance to JS
1018: // prototype-based inheritance if requested to do so.
1019: Scriptable super Proto = null;
1020: if (mapInheritance) {
1021: Class super Class = clazz.getSuperclass();
1022: if (ScriptRuntime.ScriptableClass
1023: .isAssignableFrom(super Class)
1024: && !Modifier.isAbstract(super Class.getModifiers())) {
1025: String name = ScriptableObject.defineClass(scope,
1026: super Class, sealed, mapInheritance);
1027: if (name != null) {
1028: super Proto = ScriptableObject.getClassPrototype(
1029: scope, name);
1030: }
1031: }
1032: }
1033: if (super Proto == null) {
1034: super Proto = ScriptableObject.getObjectPrototype(scope);
1035: }
1036: proto.setPrototype(super Proto);
1037:
1038: // Find out whether there are any methods that begin with
1039: // "js". If so, then only methods that begin with special
1040: // prefixes will be defined as JavaScript entities.
1041: final String functionPrefix = "jsFunction_";
1042: final String staticFunctionPrefix = "jsStaticFunction_";
1043: final String getterPrefix = "jsGet_";
1044: final String setterPrefix = "jsSet_";
1045: final String ctorName = "jsConstructor";
1046:
1047: Member ctorMember = FunctionObject.findSingleMethod(methods,
1048: ctorName);
1049:
1050: if (ctorMember == null) {
1051: if (ctors.length == 1) {
1052: ctorMember = ctors[0];
1053: } else if (ctors.length == 2) {
1054: if (ctors[0].getParameterTypes().length == 0)
1055: ctorMember = ctors[1];
1056: else if (ctors[1].getParameterTypes().length == 0)
1057: ctorMember = ctors[0];
1058: }
1059: if (ctorMember == null) {
1060: throw Context.reportRuntimeError1(
1061: "msg.ctor.multiple.parms", clazz.getName());
1062: }
1063: }
1064:
1065: FunctionObject ctor = new FunctionObject(className, ctorMember,
1066: scope);
1067: if (ctor.isVarArgsMethod()) {
1068: throw Context.reportRuntimeError1("msg.varargs.ctor",
1069: ctorMember.getName());
1070: }
1071: ctor.initAsConstructor(scope, proto);
1072:
1073: Method finishInit = null;
1074: for (int i = 0; i < methods.length; i++) {
1075: if (methods[i] == ctorMember) {
1076: continue;
1077: }
1078: String name = methods[i].getName();
1079: if (name.equals("finishInit")) {
1080: Class[] parmTypes = methods[i].getParameterTypes();
1081: if (parmTypes.length == 3
1082: && parmTypes[0] == ScriptRuntime.ScriptableClass
1083: && parmTypes[1] == FunctionObject.class
1084: && parmTypes[2] == ScriptRuntime.ScriptableClass
1085: && Modifier.isStatic(methods[i].getModifiers())) {
1086: finishInit = methods[i];
1087: continue;
1088: }
1089: }
1090: // ignore any compiler generated methods.
1091: if (name.indexOf('$') != -1)
1092: continue;
1093: if (name.equals(ctorName))
1094: continue;
1095:
1096: String prefix = null;
1097: if (name.startsWith(functionPrefix)) {
1098: prefix = functionPrefix;
1099: } else if (name.startsWith(staticFunctionPrefix)) {
1100: prefix = staticFunctionPrefix;
1101: if (!Modifier.isStatic(methods[i].getModifiers())) {
1102: throw Context
1103: .reportRuntimeError("jsStaticFunction must be used with static method.");
1104: }
1105: } else if (name.startsWith(getterPrefix)) {
1106: prefix = getterPrefix;
1107: } else if (name.startsWith(setterPrefix)) {
1108: prefix = setterPrefix;
1109: } else {
1110: continue;
1111: }
1112: name = name.substring(prefix.length());
1113: if (prefix == setterPrefix)
1114: continue; // deal with set when we see get
1115: if (prefix == getterPrefix) {
1116: if (!(proto instanceof ScriptableObject)) {
1117: throw Context.reportRuntimeError2(
1118: "msg.extend.scriptable", proto.getClass()
1119: .toString(), name);
1120: }
1121: Method setter = FunctionObject.findSingleMethod(
1122: methods, setterPrefix + name);
1123: int attr = ScriptableObject.PERMANENT
1124: | ScriptableObject.DONTENUM
1125: | (setter != null ? 0
1126: : ScriptableObject.READONLY);
1127: ((ScriptableObject) proto).defineProperty(name, null,
1128: methods[i], setter, attr);
1129: continue;
1130: }
1131:
1132: FunctionObject f = new FunctionObject(name, methods[i],
1133: proto);
1134: if (f.isVarArgsConstructor()) {
1135: throw Context.reportRuntimeError1("msg.varargs.fun",
1136: ctorMember.getName());
1137: }
1138: Scriptable dest = prefix == staticFunctionPrefix ? ctor
1139: : proto;
1140: defineProperty(dest, name, f, DONTENUM);
1141: if (sealed) {
1142: f.sealObject();
1143: }
1144: }
1145:
1146: // Call user code to complete initialization if necessary.
1147: if (finishInit != null) {
1148: Object[] finishArgs = { scope, ctor, proto };
1149: finishInit.invoke(null, finishArgs);
1150: }
1151:
1152: // Seal the object if necessary.
1153: if (sealed) {
1154: ctor.sealObject();
1155: if (proto instanceof ScriptableObject) {
1156: ((ScriptableObject) proto).sealObject();
1157: }
1158: }
1159:
1160: return ctor;
1161: }
1162:
1163: /**
1164: * Define a JavaScript property.
1165: *
1166: * Creates the property with an initial value and sets its attributes.
1167: *
1168: * @param propertyName the name of the property to define.
1169: * @param value the initial value of the property
1170: * @param attributes the attributes of the JavaScript property
1171: * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
1172: */
1173: public void defineProperty(String propertyName, Object value,
1174: int attributes) {
1175: checkNotSealed(propertyName, 0);
1176: put(propertyName, this , value);
1177: setAttributes(propertyName, attributes);
1178: }
1179:
1180: /**
1181: * Utility method to add properties to arbitrary Scriptable object.
1182: * If destination is instance of ScriptableObject, calls
1183: * defineProperty there, otherwise calls put in destination
1184: * ignoring attributes
1185: */
1186: public static void defineProperty(Scriptable destination,
1187: String propertyName, Object value, int attributes) {
1188: if (!(destination instanceof ScriptableObject)) {
1189: destination.put(propertyName, destination, value);
1190: return;
1191: }
1192: ScriptableObject so = (ScriptableObject) destination;
1193: so.defineProperty(propertyName, value, attributes);
1194: }
1195:
1196: /**
1197: * Utility method to add properties to arbitrary Scriptable object.
1198: * If destination is instance of ScriptableObject, calls
1199: * defineProperty there, otherwise calls put in destination
1200: * ignoring attributes
1201: */
1202: public static void defineConstProperty(Scriptable destination,
1203: String propertyName) {
1204: if (destination instanceof ConstProperties) {
1205: ConstProperties cp = (ConstProperties) destination;
1206: cp.defineConst(propertyName, destination);
1207: } else
1208: defineProperty(destination, propertyName,
1209: Undefined.instance, CONST);
1210: }
1211:
1212: /**
1213: * Define a JavaScript property with getter and setter side effects.
1214: *
1215: * If the setter is not found, the attribute READONLY is added to
1216: * the given attributes. <p>
1217: *
1218: * The getter must be a method with zero parameters, and the setter, if
1219: * found, must be a method with one parameter.<p>
1220: *
1221: * @param propertyName the name of the property to define. This name
1222: * also affects the name of the setter and getter
1223: * to search for. If the propertyId is "foo", then
1224: * <code>clazz</code> will be searched for "getFoo"
1225: * and "setFoo" methods.
1226: * @param clazz the Java class to search for the getter and setter
1227: * @param attributes the attributes of the JavaScript property
1228: * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
1229: */
1230: public void defineProperty(String propertyName, Class clazz,
1231: int attributes) {
1232: int length = propertyName.length();
1233: if (length == 0)
1234: throw new IllegalArgumentException();
1235: char[] buf = new char[3 + length];
1236: propertyName.getChars(0, length, buf, 3);
1237: buf[3] = Character.toUpperCase(buf[3]);
1238: buf[0] = 'g';
1239: buf[1] = 'e';
1240: buf[2] = 't';
1241: String getterName = new String(buf);
1242: buf[0] = 's';
1243: String setterName = new String(buf);
1244:
1245: Method[] methods = FunctionObject.getMethodList(clazz);
1246: Method getter = FunctionObject.findSingleMethod(methods,
1247: getterName);
1248: Method setter = FunctionObject.findSingleMethod(methods,
1249: setterName);
1250: if (setter == null)
1251: attributes |= ScriptableObject.READONLY;
1252: defineProperty(propertyName, null, getter,
1253: setter == null ? null : setter, attributes);
1254: }
1255:
1256: /**
1257: * Define a JavaScript property.
1258: *
1259: * Use this method only if you wish to define getters and setters for
1260: * a given property in a ScriptableObject. To create a property without
1261: * special getter or setter side effects, use
1262: * <code>defineProperty(String,int)</code>.
1263: *
1264: * If <code>setter</code> is null, the attribute READONLY is added to
1265: * the given attributes.<p>
1266: *
1267: * Several forms of getters or setters are allowed. In all cases the
1268: * type of the value parameter can be any one of the following types:
1269: * Object, String, boolean, Scriptable, byte, short, int, long, float,
1270: * or double. The runtime will perform appropriate conversions based
1271: * upon the type of the parameter (see description in FunctionObject).
1272: * The first forms are nonstatic methods of the class referred to
1273: * by 'this':
1274: * <pre>
1275: * Object getFoo();
1276: * void setFoo(SomeType value);</pre>
1277: * Next are static methods that may be of any class; the object whose
1278: * property is being accessed is passed in as an extra argument:
1279: * <pre>
1280: * static Object getFoo(Scriptable obj);
1281: * static void setFoo(Scriptable obj, SomeType value);</pre>
1282: * Finally, it is possible to delegate to another object entirely using
1283: * the <code>delegateTo</code> parameter. In this case the methods are
1284: * nonstatic methods of the class delegated to, and the object whose
1285: * property is being accessed is passed in as an extra argument:
1286: * <pre>
1287: * Object getFoo(Scriptable obj);
1288: * void setFoo(Scriptable obj, SomeType value);</pre>
1289: *
1290: * @param propertyName the name of the property to define.
1291: * @param delegateTo an object to call the getter and setter methods on,
1292: * or null, depending on the form used above.
1293: * @param getter the method to invoke to get the value of the property
1294: * @param setter the method to invoke to set the value of the property
1295: * @param attributes the attributes of the JavaScript property
1296: */
1297: public void defineProperty(String propertyName, Object delegateTo,
1298: Method getter, Method setter, int attributes) {
1299: MemberBox getterBox = null;
1300: if (getter != null) {
1301: getterBox = new MemberBox(getter);
1302:
1303: boolean delegatedForm;
1304: if (!Modifier.isStatic(getter.getModifiers())) {
1305: delegatedForm = (delegateTo != null);
1306: getterBox.delegateTo = delegateTo;
1307: } else {
1308: delegatedForm = true;
1309: // Ignore delegateTo for static getter but store
1310: // non-null delegateTo indicator.
1311: getterBox.delegateTo = Void.TYPE;
1312: }
1313:
1314: String errorId = null;
1315: Class[] parmTypes = getter.getParameterTypes();
1316: if (parmTypes.length == 0) {
1317: if (delegatedForm) {
1318: errorId = "msg.obj.getter.parms";
1319: }
1320: } else if (parmTypes.length == 1) {
1321: Object argType = parmTypes[0];
1322: // Allow ScriptableObject for compatibility
1323: if (!(argType == ScriptRuntime.ScriptableClass || argType == ScriptRuntime.ScriptableObjectClass)) {
1324: errorId = "msg.bad.getter.parms";
1325: } else if (!delegatedForm) {
1326: errorId = "msg.bad.getter.parms";
1327: }
1328: } else {
1329: errorId = "msg.bad.getter.parms";
1330: }
1331: if (errorId != null) {
1332: throw Context.reportRuntimeError1(errorId, getter
1333: .toString());
1334: }
1335: }
1336:
1337: MemberBox setterBox = null;
1338: if (setter != null) {
1339: if (setter.getReturnType() != Void.TYPE)
1340: throw Context.reportRuntimeError1("msg.setter.return",
1341: setter.toString());
1342:
1343: setterBox = new MemberBox(setter);
1344:
1345: boolean delegatedForm;
1346: if (!Modifier.isStatic(setter.getModifiers())) {
1347: delegatedForm = (delegateTo != null);
1348: setterBox.delegateTo = delegateTo;
1349: } else {
1350: delegatedForm = true;
1351: // Ignore delegateTo for static setter but store
1352: // non-null delegateTo indicator.
1353: setterBox.delegateTo = Void.TYPE;
1354: }
1355:
1356: String errorId = null;
1357: Class[] parmTypes = setter.getParameterTypes();
1358: if (parmTypes.length == 1) {
1359: if (delegatedForm) {
1360: errorId = "msg.setter2.expected";
1361: }
1362: } else if (parmTypes.length == 2) {
1363: Object argType = parmTypes[0];
1364: // Allow ScriptableObject for compatibility
1365: if (!(argType == ScriptRuntime.ScriptableClass || argType == ScriptRuntime.ScriptableObjectClass)) {
1366: errorId = "msg.setter2.parms";
1367: } else if (!delegatedForm) {
1368: errorId = "msg.setter1.parms";
1369: }
1370: } else {
1371: errorId = "msg.setter.parms";
1372: }
1373: if (errorId != null) {
1374: throw Context.reportRuntimeError1(errorId, setter
1375: .toString());
1376: }
1377: }
1378:
1379: GetterSlot gslot = (GetterSlot) getSlot(propertyName, 0,
1380: SLOT_MODIFY_GETTER_SETTER);
1381: gslot.setAttributes(attributes);
1382: gslot.getter = getterBox;
1383: gslot.setter = setterBox;
1384: }
1385:
1386: /**
1387: * Search for names in a class, adding the resulting methods
1388: * as properties.
1389: *
1390: * <p> Uses reflection to find the methods of the given names. Then
1391: * FunctionObjects are constructed from the methods found, and
1392: * are added to this object as properties with the given names.
1393: *
1394: * @param names the names of the Methods to add as function properties
1395: * @param clazz the class to search for the Methods
1396: * @param attributes the attributes of the new properties
1397: * @see org.mozilla.javascript.FunctionObject
1398: */
1399: public void defineFunctionProperties(String[] names, Class clazz,
1400: int attributes) {
1401: Method[] methods = FunctionObject.getMethodList(clazz);
1402: for (int i = 0; i < names.length; i++) {
1403: String name = names[i];
1404: Method m = FunctionObject.findSingleMethod(methods, name);
1405: if (m == null) {
1406: throw Context.reportRuntimeError2(
1407: "msg.method.not.found", name, clazz.getName());
1408: }
1409: FunctionObject f = new FunctionObject(name, m, this );
1410: defineProperty(name, f, attributes);
1411: }
1412: }
1413:
1414: /**
1415: * Get the Object.prototype property.
1416: * See ECMA 15.2.4.
1417: */
1418: public static Scriptable getObjectPrototype(Scriptable scope) {
1419: return getClassPrototype(scope, "Object");
1420: }
1421:
1422: /**
1423: * Get the Function.prototype property.
1424: * See ECMA 15.3.4.
1425: */
1426: public static Scriptable getFunctionPrototype(Scriptable scope) {
1427: return getClassPrototype(scope, "Function");
1428: }
1429:
1430: /**
1431: * Get the prototype for the named class.
1432: *
1433: * For example, <code>getClassPrototype(s, "Date")</code> will first
1434: * walk up the parent chain to find the outermost scope, then will
1435: * search that scope for the Date constructor, and then will
1436: * return Date.prototype. If any of the lookups fail, or
1437: * the prototype is not a JavaScript object, then null will
1438: * be returned.
1439: *
1440: * @param scope an object in the scope chain
1441: * @param className the name of the constructor
1442: * @return the prototype for the named class, or null if it
1443: * cannot be found.
1444: */
1445: public static Scriptable getClassPrototype(Scriptable scope,
1446: String className) {
1447: scope = getTopLevelScope(scope);
1448: Object ctor = getProperty(scope, className);
1449: Object proto;
1450: if (ctor instanceof BaseFunction) {
1451: proto = ((BaseFunction) ctor).getPrototypeProperty();
1452: } else if (ctor instanceof Scriptable) {
1453: Scriptable ctorObj = (Scriptable) ctor;
1454: proto = ctorObj.get("prototype", ctorObj);
1455: } else {
1456: return null;
1457: }
1458: if (proto instanceof Scriptable) {
1459: return (Scriptable) proto;
1460: }
1461: return null;
1462: }
1463:
1464: /**
1465: * Get the global scope.
1466: *
1467: * <p>Walks the parent scope chain to find an object with a null
1468: * parent scope (the global object).
1469: *
1470: * @param obj a JavaScript object
1471: * @return the corresponding global scope
1472: */
1473: public static Scriptable getTopLevelScope(Scriptable obj) {
1474: for (;;) {
1475: Scriptable parent = obj.getParentScope();
1476: if (parent == null) {
1477: return obj;
1478: }
1479: obj = parent;
1480: }
1481: }
1482:
1483: /**
1484: * Seal this object.
1485: *
1486: * A sealed object may not have properties added or removed. Once
1487: * an object is sealed it may not be unsealed.
1488: *
1489: * @since 1.4R3
1490: */
1491: public synchronized void sealObject() {
1492: if (count >= 0) {
1493: count = ~count;
1494: }
1495: }
1496:
1497: /**
1498: * Return true if this object is sealed.
1499: *
1500: * It is an error to attempt to add or remove properties to
1501: * a sealed object.
1502: *
1503: * @return true if sealed, false otherwise.
1504: * @since 1.4R3
1505: */
1506: public final boolean isSealed() {
1507: return count < 0;
1508: }
1509:
1510: private void checkNotSealed(String name, int index) {
1511: if (!isSealed())
1512: return;
1513:
1514: String str = (name != null) ? name : Integer.toString(index);
1515: throw Context.reportRuntimeError1("msg.modify.sealed", str);
1516: }
1517:
1518: /**
1519: * Gets a named property from an object or any object in its prototype chain.
1520: * <p>
1521: * Searches the prototype chain for a property named <code>name</code>.
1522: * <p>
1523: * @param obj a JavaScript object
1524: * @param name a property name
1525: * @return the value of a property with name <code>name</code> found in
1526: * <code>obj</code> or any object in its prototype chain, or
1527: * <code>Scriptable.NOT_FOUND</code> if not found
1528: * @since 1.5R2
1529: */
1530: public static Object getProperty(Scriptable obj, String name) {
1531: Scriptable start = obj;
1532: Object result;
1533: do {
1534: result = obj.get(name, start);
1535: if (result != Scriptable.NOT_FOUND)
1536: break;
1537: obj = obj.getPrototype();
1538: } while (obj != null);
1539: return result;
1540: }
1541:
1542: /**
1543: * Gets an indexed property from an object or any object in its prototype chain.
1544: * <p>
1545: * Searches the prototype chain for a property with integral index
1546: * <code>index</code>. Note that if you wish to look for properties with numerical
1547: * but non-integral indicies, you should use getProperty(Scriptable,String) with
1548: * the string value of the index.
1549: * <p>
1550: * @param obj a JavaScript object
1551: * @param index an integral index
1552: * @return the value of a property with index <code>index</code> found in
1553: * <code>obj</code> or any object in its prototype chain, or
1554: * <code>Scriptable.NOT_FOUND</code> if not found
1555: * @since 1.5R2
1556: */
1557: public static Object getProperty(Scriptable obj, int index) {
1558: Scriptable start = obj;
1559: Object result;
1560: do {
1561: result = obj.get(index, start);
1562: if (result != Scriptable.NOT_FOUND)
1563: break;
1564: obj = obj.getPrototype();
1565: } while (obj != null);
1566: return result;
1567: }
1568:
1569: /**
1570: * Returns whether a named property is defined in an object or any object
1571: * in its prototype chain.
1572: * <p>
1573: * Searches the prototype chain for a property named <code>name</code>.
1574: * <p>
1575: * @param obj a JavaScript object
1576: * @param name a property name
1577: * @return the true if property was found
1578: * @since 1.5R2
1579: */
1580: public static boolean hasProperty(Scriptable obj, String name) {
1581: return null != getBase(obj, name);
1582: }
1583:
1584: /**
1585: * If hasProperty(obj, name) would return true, then if the property that
1586: * was found is compatible with the new property, this method just returns.
1587: * If the property is not compatible, then an exception is thrown.
1588: *
1589: * A property redefinition is incompatible if the first definition was a
1590: * const declaration or if this one is. They are compatible only if neither
1591: * was const.
1592: */
1593: public static void redefineProperty(Scriptable obj, String name,
1594: boolean isConst) {
1595: Scriptable base = getBase(obj, name);
1596: if (base == null)
1597: return;
1598: if (base instanceof ConstProperties) {
1599: ConstProperties cp = (ConstProperties) base;
1600:
1601: if (cp.isConst(name))
1602: throw Context.reportRuntimeError1("msg.const.redecl",
1603: name);
1604: }
1605: if (isConst)
1606: throw Context.reportRuntimeError1("msg.var.redecl", name);
1607: }
1608:
1609: /**
1610: * Returns whether an indexed property is defined in an object or any object
1611: * in its prototype chain.
1612: * <p>
1613: * Searches the prototype chain for a property with index <code>index</code>.
1614: * <p>
1615: * @param obj a JavaScript object
1616: * @param index a property index
1617: * @return the true if property was found
1618: * @since 1.5R2
1619: */
1620: public static boolean hasProperty(Scriptable obj, int index) {
1621: return null != getBase(obj, index);
1622: }
1623:
1624: /**
1625: * Puts a named property in an object or in an object in its prototype chain.
1626: * <p>
1627: * Searches for the named property in the prototype chain. If it is found,
1628: * the value of the property in <code>obj</code> is changed through a call
1629: * to {@link Scriptable#put(String, Scriptable, Object)} on the
1630: * prototype passing <code>obj</code> as the <code>start</code> argument.
1631: * This allows the prototype to veto the property setting in case the
1632: * prototype defines the property with [[ReadOnly]] attribute. If the
1633: * property is not found, it is added in <code>obj</code>.
1634: * @param obj a JavaScript object
1635: * @param name a property name
1636: * @param value any JavaScript value accepted by Scriptable.put
1637: * @since 1.5R2
1638: */
1639: public static void putProperty(Scriptable obj, String name,
1640: Object value) {
1641: Scriptable base = getBase(obj, name);
1642: if (base == null)
1643: base = obj;
1644: base.put(name, obj, value);
1645: }
1646:
1647: /**
1648: * Puts a named property in an object or in an object in its prototype chain.
1649: * <p>
1650: * Searches for the named property in the prototype chain. If it is found,
1651: * the value of the property in <code>obj</code> is changed through a call
1652: * to {@link Scriptable#put(String, Scriptable, Object)} on the
1653: * prototype passing <code>obj</code> as the <code>start</code> argument.
1654: * This allows the prototype to veto the property setting in case the
1655: * prototype defines the property with [[ReadOnly]] attribute. If the
1656: * property is not found, it is added in <code>obj</code>.
1657: * @param obj a JavaScript object
1658: * @param name a property name
1659: * @param value any JavaScript value accepted by Scriptable.put
1660: * @since 1.5R2
1661: */
1662: public static void putConstProperty(Scriptable obj, String name,
1663: Object value) {
1664: Scriptable base = getBase(obj, name);
1665: if (base == null)
1666: base = obj;
1667: if (base instanceof ConstProperties)
1668: ((ConstProperties) base).putConst(name, obj, value);
1669: }
1670:
1671: /**
1672: * Puts an indexed property in an object or in an object in its prototype chain.
1673: * <p>
1674: * Searches for the indexed property in the prototype chain. If it is found,
1675: * the value of the property in <code>obj</code> is changed through a call
1676: * to {@link Scriptable#put(int, Scriptable, Object)} on the prototype
1677: * passing <code>obj</code> as the <code>start</code> argument. This allows
1678: * the prototype to veto the property setting in case the prototype defines
1679: * the property with [[ReadOnly]] attribute. If the property is not found,
1680: * it is added in <code>obj</code>.
1681: * @param obj a JavaScript object
1682: * @param index a property index
1683: * @param value any JavaScript value accepted by Scriptable.put
1684: * @since 1.5R2
1685: */
1686: public static void putProperty(Scriptable obj, int index,
1687: Object value) {
1688: Scriptable base = getBase(obj, index);
1689: if (base == null)
1690: base = obj;
1691: base.put(index, obj, value);
1692: }
1693:
1694: /**
1695: * Removes the property from an object or its prototype chain.
1696: * <p>
1697: * Searches for a property with <code>name</code> in obj or
1698: * its prototype chain. If it is found, the object's delete
1699: * method is called.
1700: * @param obj a JavaScript object
1701: * @param name a property name
1702: * @return true if the property doesn't exist or was successfully removed
1703: * @since 1.5R2
1704: */
1705: public static boolean deleteProperty(Scriptable obj, String name) {
1706: Scriptable base = getBase(obj, name);
1707: if (base == null)
1708: return true;
1709: base.delete(name);
1710: return !base.has(name, obj);
1711: }
1712:
1713: /**
1714: * Removes the property from an object or its prototype chain.
1715: * <p>
1716: * Searches for a property with <code>index</code> in obj or
1717: * its prototype chain. If it is found, the object's delete
1718: * method is called.
1719: * @param obj a JavaScript object
1720: * @param index a property index
1721: * @return true if the property doesn't exist or was successfully removed
1722: * @since 1.5R2
1723: */
1724: public static boolean deleteProperty(Scriptable obj, int index) {
1725: Scriptable base = getBase(obj, index);
1726: if (base == null)
1727: return true;
1728: base.delete(index);
1729: return !base.has(index, obj);
1730: }
1731:
1732: /**
1733: * Returns an array of all ids from an object and its prototypes.
1734: * <p>
1735: * @param obj a JavaScript object
1736: * @return an array of all ids from all object in the prototype chain.
1737: * If a given id occurs multiple times in the prototype chain,
1738: * it will occur only once in this list.
1739: * @since 1.5R2
1740: */
1741: public static Object[] getPropertyIds(Scriptable obj) {
1742: if (obj == null) {
1743: return ScriptRuntime.emptyArgs;
1744: }
1745: Object[] result = obj.getIds();
1746: ObjToIntMap map = null;
1747: for (;;) {
1748: obj = obj.getPrototype();
1749: if (obj == null) {
1750: break;
1751: }
1752: Object[] ids = obj.getIds();
1753: if (ids.length == 0) {
1754: continue;
1755: }
1756: if (map == null) {
1757: if (result.length == 0) {
1758: result = ids;
1759: continue;
1760: }
1761: map = new ObjToIntMap(result.length + ids.length);
1762: for (int i = 0; i != result.length; ++i) {
1763: map.intern(result[i]);
1764: }
1765: result = null; // Allow to GC the result
1766: }
1767: for (int i = 0; i != ids.length; ++i) {
1768: map.intern(ids[i]);
1769: }
1770: }
1771: if (map != null) {
1772: result = map.getKeys();
1773: }
1774: return result;
1775: }
1776:
1777: /**
1778: * Call a method of an object.
1779: * @param obj the JavaScript object
1780: * @param methodName the name of the function property
1781: * @param args the arguments for the call
1782: *
1783: * @see Context#getCurrentContext()
1784: */
1785: public static Object callMethod(Scriptable obj, String methodName,
1786: Object[] args) {
1787: return callMethod(null, obj, methodName, args);
1788: }
1789:
1790: /**
1791: * Call a method of an object.
1792: * @param cx the Context object associated with the current thread.
1793: * @param obj the JavaScript object
1794: * @param methodName the name of the function property
1795: * @param args the arguments for the call
1796: */
1797: public static Object callMethod(Context cx, Scriptable obj,
1798: String methodName, Object[] args) {
1799: Object funObj = getProperty(obj, methodName);
1800: if (!(funObj instanceof Function)) {
1801: throw ScriptRuntime.notFunctionError(obj, methodName);
1802: }
1803: Function fun = (Function) funObj;
1804: // XXX: What should be the scope when calling funObj?
1805: // The following favor scope stored in the object on the assumption
1806: // that is more useful especially under dynamic scope setup.
1807: // An alternative is to check for dynamic scope flag
1808: // and use ScriptableObject.getTopLevelScope(fun) if the flag is not
1809: // set. But that require access to Context and messy code
1810: // so for now it is not checked.
1811: Scriptable scope = ScriptableObject.getTopLevelScope(obj);
1812: if (cx != null) {
1813: return fun.call(cx, scope, obj, args);
1814: } else {
1815: return Context.call(null, fun, scope, obj, args);
1816: }
1817: }
1818:
1819: private static Scriptable getBase(Scriptable obj, String name) {
1820: do {
1821: if (obj.has(name, obj))
1822: break;
1823: obj = obj.getPrototype();
1824: } while (obj != null);
1825: return obj;
1826: }
1827:
1828: private static Scriptable getBase(Scriptable obj, int index) {
1829: do {
1830: if (obj.has(index, obj))
1831: break;
1832: obj = obj.getPrototype();
1833: } while (obj != null);
1834: return obj;
1835: }
1836:
1837: /**
1838: * Get arbitrary application-specific value associated with this object.
1839: * @param key key object to select particular value.
1840: * @see #associateValue(Object key, Object value)
1841: */
1842: public final Object getAssociatedValue(Object key) {
1843: Hashtable h = associatedValues;
1844: if (h == null)
1845: return null;
1846: return h.get(key);
1847: }
1848:
1849: /**
1850: * Get arbitrary application-specific value associated with the top scope
1851: * of the given scope.
1852: * The method first calls {@link #getTopLevelScope(Scriptable scope)}
1853: * and then searches the prototype chain of the top scope for the first
1854: * object containing the associated value with the given key.
1855: *
1856: * @param scope the starting scope.
1857: * @param key key object to select particular value.
1858: * @see #getAssociatedValue(Object key)
1859: */
1860: public static Object getTopScopeValue(Scriptable scope, Object key) {
1861: scope = ScriptableObject.getTopLevelScope(scope);
1862: for (;;) {
1863: if (scope instanceof ScriptableObject) {
1864: ScriptableObject so = (ScriptableObject) scope;
1865: Object value = so.getAssociatedValue(key);
1866: if (value != null) {
1867: return value;
1868: }
1869: }
1870: scope = scope.getPrototype();
1871: if (scope == null) {
1872: return null;
1873: }
1874: }
1875: }
1876:
1877: /**
1878: * Associate arbitrary application-specific value with this object.
1879: * Value can only be associated with the given object and key only once.
1880: * The method ignores any subsequent attempts to change the already
1881: * associated value.
1882: * <p> The associated values are not serialized.
1883: * @param key key object to select particular value.
1884: * @param value the value to associate
1885: * @return the passed value if the method is called first time for the
1886: * given key or old value for any subsequent calls.
1887: * @see #getAssociatedValue(Object key)
1888: */
1889: public final Object associateValue(Object key, Object value) {
1890: if (value == null)
1891: throw new IllegalArgumentException();
1892: Hashtable h = associatedValues;
1893: if (h == null) {
1894: synchronized (this ) {
1895: h = associatedValues;
1896: if (h == null) {
1897: h = new Hashtable();
1898: associatedValues = h;
1899: }
1900: }
1901: }
1902: return Kit.initHash(h, key, value);
1903: }
1904:
1905: private Object getImpl(String name, int index, Scriptable start) {
1906: Slot slot = getSlot(name, index, SLOT_QUERY);
1907: if (slot == null) {
1908: return Scriptable.NOT_FOUND;
1909: }
1910: if (!(slot instanceof GetterSlot)) {
1911: return slot.value;
1912: }
1913: Object getterObj = ((GetterSlot) slot).getter;
1914: if (getterObj != null) {
1915: if (getterObj instanceof MemberBox) {
1916: MemberBox nativeGetter = (MemberBox) getterObj;
1917: Object getterThis;
1918: Object[] args;
1919: if (nativeGetter.delegateTo == null) {
1920: getterThis = start;
1921: args = ScriptRuntime.emptyArgs;
1922: } else {
1923: getterThis = nativeGetter.delegateTo;
1924: args = new Object[] { start };
1925: }
1926: return nativeGetter.invoke(getterThis, args);
1927: } else {
1928: Function f = (Function) getterObj;
1929: Context cx = Context.getContext();
1930: return f.call(cx, f.getParentScope(), start,
1931: ScriptRuntime.emptyArgs);
1932: }
1933: }
1934: Object value = slot.value;
1935: if (value instanceof LazilyLoadedCtor) {
1936: LazilyLoadedCtor initializer = (LazilyLoadedCtor) value;
1937: try {
1938: initializer.init();
1939: } finally {
1940: value = initializer.getValue();
1941: slot.value = value;
1942: }
1943: }
1944: return value;
1945: }
1946:
1947: /**
1948: *
1949: * @param name
1950: * @param index
1951: * @param start
1952: * @param value
1953: * @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means
1954: * defineConstProperty. READONLY means const initialization expression.
1955: * @return false if this != start and no slot was found. true if this == start
1956: * or this != start and a READONLY slot was found.
1957: */
1958: private boolean putImpl(String name, int index, Scriptable start,
1959: Object value, int constFlag) {
1960: Slot slot;
1961: if (this != start) {
1962: slot = getSlot(name, index, SLOT_QUERY);
1963: if (slot == null) {
1964: return false;
1965: }
1966: } else {
1967: checkNotSealed(name, index);
1968: // either const hoisted declaration or initialization
1969: if (constFlag != EMPTY) {
1970: slot = getSlot(name, index, SLOT_MODIFY_CONST);
1971: int attr = slot.getAttributes();
1972: if ((attr & READONLY) == 0)
1973: throw Context.reportRuntimeError1("msg.var.redecl",
1974: name);
1975: if ((attr & UNINITIALIZED_CONST) != 0) {
1976: slot.value = value;
1977: // clear the bit on const initialization
1978: if (constFlag != UNINITIALIZED_CONST)
1979: slot.setAttributes(attr & ~UNINITIALIZED_CONST);
1980: }
1981: return true;
1982: }
1983: slot = getSlot(name, index, SLOT_MODIFY);
1984: }
1985: if ((slot.getAttributes() & READONLY) != 0)
1986: return true;
1987: if (slot instanceof GetterSlot) {
1988: Object setterObj = ((GetterSlot) slot).setter;
1989: if (setterObj != null) {
1990: Context cx = Context.getContext();
1991: if (setterObj instanceof MemberBox) {
1992: MemberBox nativeSetter = (MemberBox) setterObj;
1993: Class pTypes[] = nativeSetter.argTypes;
1994: // XXX: cache tag since it is already calculated in
1995: // defineProperty ?
1996: Class valueType = pTypes[pTypes.length - 1];
1997: int tag = FunctionObject.getTypeTag(valueType);
1998: Object actualArg = FunctionObject.convertArg(cx,
1999: start, value, tag);
2000: Object setterThis;
2001: Object[] args;
2002: if (nativeSetter.delegateTo == null) {
2003: setterThis = start;
2004: args = new Object[] { actualArg };
2005: } else {
2006: setterThis = nativeSetter.delegateTo;
2007: args = new Object[] { start, actualArg };
2008: }
2009: nativeSetter.invoke(setterThis, args);
2010: } else {
2011: Function f = (Function) setterObj;
2012: f.call(cx, f.getParentScope(), start,
2013: new Object[] { value });
2014: }
2015: return true;
2016: }
2017: }
2018: if (this == start) {
2019: slot.value = value;
2020: return true;
2021: } else {
2022: return false;
2023: }
2024: }
2025:
2026: private Slot findAttributeSlot(String name, int index,
2027: int accessType) {
2028: Slot slot = getSlot(name, index, accessType);
2029: if (slot == null) {
2030: String str = (name != null ? name : Integer.toString(index));
2031: throw Context
2032: .reportRuntimeError1("msg.prop.not.found", str);
2033: }
2034: return slot;
2035: }
2036:
2037: /**
2038: * Locate the slot with given name or index.
2039: *
2040: * @param name property name or null if slot holds spare array index.
2041: * @param index index or 0 if slot holds property name.
2042: */
2043: private Slot getSlot(String name, int index, int accessType) {
2044: Slot slot;
2045:
2046: // Query last access cache and check that it was not deleted.
2047: lastAccessCheck: {
2048: slot = lastAccess;
2049: if (name != null) {
2050: if (name != slot.name)
2051: break lastAccessCheck;
2052: // No String.equals here as successful slot search update
2053: // name object with fresh reference of the same string.
2054: } else {
2055: if (slot.name != null || index != slot.indexOrHash)
2056: break lastAccessCheck;
2057: }
2058:
2059: if (slot.wasDeleted != 0)
2060: break lastAccessCheck;
2061:
2062: if (accessType == SLOT_MODIFY_GETTER_SETTER
2063: && !(slot instanceof GetterSlot))
2064: break lastAccessCheck;
2065:
2066: return slot;
2067: }
2068:
2069: slot = accessSlot(name, index, accessType);
2070: if (slot != null) {
2071: // Update the cache
2072: lastAccess = slot;
2073: }
2074: return slot;
2075: }
2076:
2077: private Slot accessSlot(String name, int index, int accessType) {
2078: int indexOrHash = (name != null ? name.hashCode() : index);
2079:
2080: if (accessType == SLOT_QUERY || accessType == SLOT_MODIFY
2081: || accessType == SLOT_MODIFY_CONST
2082: || accessType == SLOT_MODIFY_GETTER_SETTER) {
2083: // Check the hashtable without using synchronization
2084:
2085: Slot[] slotsLocalRef = slots; // Get stable local reference
2086: if (slotsLocalRef == null) {
2087: if (accessType == SLOT_QUERY)
2088: return null;
2089: } else {
2090: int tableSize = slotsLocalRef.length;
2091: int slotIndex = getSlotIndex(tableSize, indexOrHash);
2092: Slot slot = slotsLocalRef[slotIndex];
2093: while (slot != null) {
2094: String sname = slot.name;
2095: if (sname != null) {
2096: if (sname == name)
2097: break;
2098: if (name != null
2099: && indexOrHash == slot.indexOrHash) {
2100: if (name.equals(sname)) {
2101: // This will avoid calling String.equals when
2102: // slot is accessed with same string object
2103: // next time.
2104: slot.name = name;
2105: break;
2106: }
2107: }
2108: } else if (name == null
2109: && indexOrHash == slot.indexOrHash) {
2110: break;
2111: }
2112: slot = slot.next;
2113: }
2114: if (accessType == SLOT_QUERY) {
2115: return slot;
2116: } else if (accessType == SLOT_MODIFY) {
2117: if (slot != null)
2118: return slot;
2119: } else if (accessType == SLOT_MODIFY_GETTER_SETTER) {
2120: if (slot instanceof GetterSlot)
2121: return slot;
2122: } else if (accessType == SLOT_MODIFY_CONST) {
2123: if (slot != null)
2124: return slot;
2125: }
2126: }
2127:
2128: // A new slot has to be inserted or the old has to be replaced
2129: // by GetterSlot. Time to synchronize.
2130:
2131: synchronized (this ) {
2132: // Refresh local ref if another thread triggered grow
2133: slotsLocalRef = slots;
2134: int insertPos;
2135: if (count == 0) {
2136: // Always throw away old slots if any on empty insert
2137: slotsLocalRef = new Slot[5];
2138: slots = slotsLocalRef;
2139: insertPos = getSlotIndex(slotsLocalRef.length,
2140: indexOrHash);
2141: } else {
2142: int tableSize = slotsLocalRef.length;
2143: insertPos = getSlotIndex(tableSize, indexOrHash);
2144: Slot prev = slotsLocalRef[insertPos];
2145: Slot slot = prev;
2146: while (slot != null) {
2147: if (slot.indexOrHash == indexOrHash
2148: && (slot.name == name || (name != null && name
2149: .equals(slot.name)))) {
2150: break;
2151: }
2152: prev = slot;
2153: slot = slot.next;
2154: }
2155:
2156: if (slot != null) {
2157: // Another thread just added a slot with same
2158: // name/index before this one entered synchronized
2159: // block. This is a race in application code and
2160: // probably indicates bug there. But for the hashtable
2161: // implementation it is harmless with the only
2162: // complication is the need to replace the added slot
2163: // if we need GetterSlot and the old one is not.
2164: if (accessType == SLOT_MODIFY_GETTER_SETTER
2165: && !(slot instanceof GetterSlot)) {
2166: GetterSlot newSlot = new GetterSlot(name,
2167: indexOrHash, slot.getAttributes());
2168: newSlot.value = slot.value;
2169: newSlot.next = slot.next;
2170: if (prev == slot) {
2171: slotsLocalRef[insertPos] = newSlot;
2172: } else {
2173: prev.next = newSlot;
2174: }
2175: slot.wasDeleted = (byte) 1;
2176: if (slot == lastAccess) {
2177: lastAccess = REMOVED;
2178: }
2179: slot = newSlot;
2180: } else if (accessType == SLOT_MODIFY_CONST) {
2181: return null;
2182: }
2183: return slot;
2184: }
2185:
2186: // Check if the table is not too full before inserting.
2187: if (4 * (count + 1) > 3 * slotsLocalRef.length) {
2188: slotsLocalRef = new Slot[slotsLocalRef.length * 2 + 1];
2189: copyTable(slots, slotsLocalRef, count);
2190: slots = slotsLocalRef;
2191: insertPos = getSlotIndex(slotsLocalRef.length,
2192: indexOrHash);
2193: }
2194: }
2195:
2196: Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER ? new GetterSlot(
2197: name, indexOrHash, 0)
2198: : new Slot(name, indexOrHash, 0));
2199: if (accessType == SLOT_MODIFY_CONST)
2200: newSlot.setAttributes(CONST);
2201: ++count;
2202: addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos);
2203: return newSlot;
2204: }
2205:
2206: } else if (accessType == SLOT_REMOVE) {
2207: synchronized (this ) {
2208: Slot[] slotsLocalRef = slots;
2209: if (count != 0) {
2210: int tableSize = slots.length;
2211: int slotIndex = getSlotIndex(tableSize, indexOrHash);
2212: Slot prev = slotsLocalRef[slotIndex];
2213: Slot slot = prev;
2214: while (slot != null) {
2215: if (slot.indexOrHash == indexOrHash
2216: && (slot.name == name || (name != null && name
2217: .equals(slot.name)))) {
2218: break;
2219: }
2220: prev = slot;
2221: slot = slot.next;
2222: }
2223: if (slot != null
2224: && (slot.getAttributes() & PERMANENT) == 0) {
2225: count--;
2226: if (prev == slot) {
2227: slotsLocalRef[slotIndex] = slot.next;
2228: } else {
2229: prev.next = slot.next;
2230: }
2231: // Mark the slot as removed to handle a case when
2232: // another thread manages to put just removed slot
2233: // into lastAccess cache.
2234: slot.wasDeleted = (byte) 1;
2235: if (slot == lastAccess) {
2236: lastAccess = REMOVED;
2237: }
2238: }
2239: }
2240: }
2241: return null;
2242:
2243: } else {
2244: throw Kit.codeBug();
2245: }
2246: }
2247:
2248: private static int getSlotIndex(int tableSize, int indexOrHash) {
2249: return (indexOrHash & 0x7fffffff) % tableSize;
2250: }
2251:
2252: // Must be inside synchronized (this)
2253: private static void copyTable(Slot[] slots, Slot[] newSlots,
2254: int count) {
2255: if (count == 0)
2256: throw Kit.codeBug();
2257:
2258: int tableSize = newSlots.length;
2259: int i = slots.length;
2260: for (;;) {
2261: --i;
2262: Slot slot = slots[i];
2263: while (slot != null) {
2264: int insertPos = getSlotIndex(tableSize,
2265: slot.indexOrHash);
2266: Slot next = slot.next;
2267: addKnownAbsentSlot(newSlots, slot, insertPos);
2268: slot.next = null;
2269: slot = next;
2270: if (--count == 0)
2271: return;
2272: }
2273: }
2274: }
2275:
2276: /**
2277: * Add slot with keys that are known to absent from the table.
2278: * This is an optimization to use when inserting into empty table,
2279: * after table growth or during deserialization.
2280: */
2281: private static void addKnownAbsentSlot(Slot[] slots, Slot slot,
2282: int insertPos) {
2283: if (slots[insertPos] == null) {
2284: slots[insertPos] = slot;
2285: } else {
2286: Slot prev = slots[insertPos];
2287: while (prev.next != null) {
2288: prev = prev.next;
2289: }
2290: prev.next = slot;
2291: }
2292: }
2293:
2294: Object[] getIds(boolean getAll) {
2295: Slot[] s = slots;
2296: Object[] a = ScriptRuntime.emptyArgs;
2297: if (s == null)
2298: return a;
2299: int c = 0;
2300: for (int i = 0; i < s.length; i++) {
2301: Slot slot = s[i];
2302: while (slot != null) {
2303: if (getAll || (slot.getAttributes() & DONTENUM) == 0) {
2304: if (c == 0)
2305: a = new Object[s.length];
2306: a[c++] = (slot.name != null ? (Object) slot.name
2307: : new Integer(slot.indexOrHash));
2308: }
2309: slot = slot.next;
2310: }
2311: }
2312: if (c == a.length)
2313: return a;
2314: Object[] result = new Object[c];
2315: System.arraycopy(a, 0, result, 0, c);
2316: return result;
2317: }
2318:
2319: private synchronized void writeObject(ObjectOutputStream out)
2320: throws IOException {
2321: out.defaultWriteObject();
2322: int objectsCount = count;
2323: if (objectsCount < 0) {
2324: // "this" was sealed
2325: objectsCount = ~objectsCount;
2326: }
2327: if (objectsCount == 0) {
2328: out.writeInt(0);
2329: } else {
2330: out.writeInt(slots.length);
2331: for (int i = 0; i < slots.length; ++i) {
2332: Slot slot = slots[i];
2333: while (slot != null) {
2334: out.writeObject(slot);
2335: slot = slot.next;
2336: if (--objectsCount == 0)
2337: return;
2338: }
2339: }
2340: }
2341: }
2342:
2343: private void readObject(ObjectInputStream in) throws IOException,
2344: ClassNotFoundException {
2345: in.defaultReadObject();
2346: lastAccess = REMOVED;
2347:
2348: int tableSize = in.readInt();
2349: if (tableSize != 0) {
2350: slots = new Slot[tableSize];
2351: int objectsCount = count;
2352: if (objectsCount < 0) {
2353: // "this" was sealed
2354: objectsCount = ~objectsCount;
2355: }
2356: for (int i = 0; i != objectsCount; ++i) {
2357: Slot slot = (Slot) in.readObject();
2358: int slotIndex = getSlotIndex(tableSize,
2359: slot.indexOrHash);
2360: addKnownAbsentSlot(slots, slot, slotIndex);
2361: }
2362: }
2363: }
2364:
2365: }
|