gt:                 tryToString = (i == 0);
context: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
   *
   * ***** BEGIN LICENSE BLOCK *****
   * Version: MPL 1.1/GPL 2.0
   *
   * The contents of this file are subject to the Mozilla Public License Version
   * 1.1 (the "License"); you may not use this file except in compliance with
   * the License. You may obtain a copy of the License at
   * http://www.mozilla.org/MPL/
   *
   * Software distributed under the License is distributed on an "AS IS" basis,
   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   * for the specific language governing rights and limitations under the
   * License.
   *
   * The Original Code is Rhino code, released
   * May 6, 1999.
   *
   * The Initial Developer of the Original Code is
   * Netscape Communications Corporation.
   * Portions created by the Initial Developer are Copyright (C) 1997-1999
   * the Initial Developer. All Rights Reserved.
   *
   * Contributor(s):
   *   Norris Boyd
   *   Igor Bukanov
   *   Daniel Gredler
   *   Bob Jervis
   *   Roger Lawrence
   *   Cameron McCormack
   *   Steve Weiss
   *
   * Alternatively, the contents of this file may be used under the terms of
   * the GNU General Public License Version 2 or later (the "GPL"), in which
   * case the provisions of the GPL are applicable instead of those above. If
   * you wish to allow use of your version of this file only under the terms of
   * the GPL and not to allow others to use your version of this file under the
   * MPL, indicate your decision by deleting the provisions above and replacing
   * them with the notice and other provisions required by the GPL. If you do
   * not delete the provisions above, a recipient may use your version of this
   * file under either the MPL or the GPL.
   *
   * ***** END LICENSE BLOCK ***** */
  
  // API class
  
  package org.mozilla.javascript;
  
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.io.Serializable;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Member;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  
  import org.mozilla.javascript.debug.DebuggableObject;
  
  /**
   * This is the default implementation of the Scriptable interface. This
   * class provides convenient default behavior that makes it easier to
   * define host objects.
   * <p>
   * Various properties and methods of JavaScript objects can be conveniently
   * defined using methods of ScriptableObject.
   * <p>
   * Classes extending ScriptableObject must define the getClassName method.
   *
   * @see org.mozilla.javascript.Scriptable
   * @author Norris Boyd
   */
  
  public abstract class ScriptableObject implements Scriptable, Serializable,
                                                    DebuggableObject,
                                                    ConstProperties
  {
  
      /**
       * The empty property attribute.
       *
       * Used by getAttributes() and setAttributes().
       *
       * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
       * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
       */
      public static final int EMPTY =     0x00;
  
      /**
       * Property attribute indicating assignment to this property is ignored.
       *
       * @see org.mozilla.javascript.ScriptableObject
       *      #put(String, Scriptable, Object)
       * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
       * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
       */
      public static final int READONLY =  0x01;
  
      /**
       * Property attribute indicating property is not enumerated.
       *
       * Only enumerated properties will be returned by getIds().
       *
       * @see org.mozilla.javascript.ScriptableObject#getIds()
       * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
       * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
       */
      public static final int DONTENUM =  0x02;
  
      /**
       * Property attribute indicating property cannot be deleted.
       *
       * @see org.mozilla.javascript.ScriptableObject#delete(String)
       * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
       * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
       */
      public static final int PERMANENT = 0x04;
  
      /**
       * Property attribute indicating that this is a const property that has not
       * been assigned yet.  The first 'const' assignment to the property will
       * clear this bit.
       */
      public static final int UNINITIALIZED_CONST = 0x08;
  
      public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST;
      /**
       * The prototype of this object.
       */
      private Scriptable prototypeObject;
  
      /**
       * The parent scope of this object.
       */
      private Scriptable parentScopeObject;
  
      private static final Slot REMOVED = new Slot(null, 0, READONLY);
  
      static {
          REMOVED.wasDeleted = true;
      }
  
      private transient Slot[] slots;
      // If count >= 0, it gives number of keys or if count < 0,
      // it indicates sealed object where ~count gives number of keys
      private int count;
  
      // gateways into the definition-order linked list of slots
      private transient Slot firstAdded;
      private transient Slot lastAdded;
  
      // cache; may be removed for smaller memory footprint
      private transient Slot lastAccess = REMOVED;
  
      private volatile Map<Object,Object> associatedValues;
  
      private static final int SLOT_QUERY = 1;
      private static final int SLOT_MODIFY = 2;
      private static final int SLOT_REMOVE = 3;
      private static final int SLOT_MODIFY_GETTER_SETTER = 4;
      private static final int SLOT_MODIFY_CONST = 5;
  
      private static class Slot implements Serializable
      {
          private static final long serialVersionUID = -6090581677123995491L;
          String name; // This can change due to caching
          int indexOrHash;
          private volatile short attributes;
          transient volatile boolean wasDeleted;
          volatile Object value;
          transient volatile Slot next; // next in hash table bucket
          transient volatile Slot orderedNext; // next in linked list
  
          Slot(String name, int indexOrHash, int attributes)
          {
              this.name = name;
              this.indexOrHash = indexOrHash;
              this.attributes = (short)attributes;
          }
  
          private void readObject(ObjectInputStream in)
              throws IOException, ClassNotFoundException
          {
              in.defaultReadObject();
              if (name != null) {
                  indexOrHash = name.hashCode();
              }
          }
  
          final int getAttributes()
          {
              return attributes;
          }
  
          final synchronized void setAttributes(int value)
          {
              checkValidAttributes(value);
              attributes = (short)value;
          }
  
          final void checkNotReadonly()
          {
              if ((attributes & READONLY) != 0) {
                  String str = (name != null ? name
                                : Integer.toString(indexOrHash));
                  throw Context.reportRuntimeError1("msg.modify.readonly", str);
              }
          }
  
      }
  
      private static final class GetterSlot extends Slot
      {
          static final long serialVersionUID = -4900574849788797588L;
  
          Object getter;
          Object setter;
  
          GetterSlot(String name, int indexOrHash, int attributes)
          {
              super(name, indexOrHash, attributes);
          }
      }
  
      static void checkValidAttributes(int attributes)
      {
          final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST;
          if ((attributes & ~mask) != 0) {
              throw new IllegalArgumentException(String.valueOf(attributes));
          }
      }
  
      public ScriptableObject()
      {
      }
  
      public ScriptableObject(Scriptable scope, Scriptable prototype)
      {
          if (scope == null)
              throw new IllegalArgumentException();
  
          parentScopeObject = scope;
          prototypeObject = prototype;
      }
  
      /**
       * Return the name of the class.
       *
       * This is typically the same name as the constructor.
       * Classes extending ScriptableObject must implement this abstract
       * method.
       */
      public abstract String getClassName();
  
      /**
       * Returns true if the named property is defined.
       *
       * @param name the name of the property
       * @param start the object in which the lookup began
       * @return true if and only if the property was found in the object
       */
      public boolean has(String name, Scriptable start)
      {
          return null != getSlot(name, 0, SLOT_QUERY);
      }
  
      /**
       * Returns true if the property index is defined.
       *
       * @param index the numeric index for the property
       * @param start the object in which the lookup began
       * @return true if and only if the property was found in the object
       */
      public boolean has(int index, Scriptable start)
      {
          return null != getSlot(null, index, SLOT_QUERY);
      }
  
      /**
       * Returns the value of the named property or NOT_FOUND.
       *
       * If the property was created using defineProperty, the
       * appropriate getter method is called.
       *
       * @param name the name of the property
       * @param start the object in which the lookup began
       * @return the value of the property (may be null), or NOT_FOUND
       */
      public Object get(String name, Scriptable start)
      {
          return getImpl(name, 0, start);
      }
  
      /**
       * Returns the value of the indexed property or NOT_FOUND.
       *
       * @param index the numeric index for the property
       * @param start the object in which the lookup began
       * @return the value of the property (may be null), or NOT_FOUND
       */
      public Object get(int index, Scriptable start)
      {
          return getImpl(null, index, start);
      }
  
      /**
       * Sets the value of the named property, creating it if need be.
       *
       * If the property was created using defineProperty, the
       * appropriate setter method is called. <p>
       *
       * If the property's attributes include READONLY, no action is
       * taken.
       * This method will actually set the property in the start
       * object.
       *
       * @param name the name of the property
       * @param start the object whose property is being set
       * @param value value to set the property to
       */
      public void put(String name, Scriptable start, Object value)
      {
          if (putImpl(name, 0, start, value, EMPTY))
              return;
  
          if (start == this) throw Kit.codeBug();
          start.put(name, start, value);
      }
  
      /**
       * Sets the value of the indexed property, creating it if need be.
       *
       * @param index the numeric index for the property
       * @param start the object whose property is being set
       * @param value value to set the property to
       */
      public void put(int index, Scriptable start, Object value)
      {
          if (putImpl(null, index, start, value, EMPTY))
              return;
  
          if (start == this) throw Kit.codeBug();
          start.put(index, start, value);
      }
  
      /**
       * Removes a named property from the object.
       *
       * If the property is not found, or it has the PERMANENT attribute,
       * no action is taken.
       *
       * @param name the name of the property
       */
      public void delete(String name)
      {
          checkNotSealed(name, 0);
          accessSlot(name, 0, SLOT_REMOVE);
      }
  
      /**
       * Removes the indexed property from the object.
       *
       * If the property is not found, or it has the PERMANENT attribute,
       * no action is taken.
       *
       * @param index the numeric index for the property
       */
      public void delete(int index)
      {
          checkNotSealed(null, index);
          accessSlot(null, index, SLOT_REMOVE);
      }
  
      /**
       * Sets the value of the named const property, creating it if need be.
       *
       * If the property was created using defineProperty, the
       * appropriate setter method is called. <p>
       *
       * If the property's attributes include READONLY, no action is
       * taken.
       * This method will actually set the property in the start
       * object.
       *
       * @param name the name of the property
       * @param start the object whose property is being set
       * @param value value to set the property to
       */
      public void putConst(String name, Scriptable start, Object value)
      {
          if (putImpl(name, 0, start, value, READONLY))
              return;
  
          if (start == this) throw Kit.codeBug();
          if (start instanceof ConstProperties)
              ((ConstProperties)start).putConst(name, start, value);
          else
              start.put(name, start, value);
      }
  
      public void defineConst(String name, Scriptable start)
      {
          if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST))
              return;
  
          if (start == this) throw Kit.codeBug();
          if (start instanceof ConstProperties)
              ((ConstProperties)start).defineConst(name, start);
      }
      /**
       * Returns true if the named property is defined as a const on this object.
       * @param name
       * @return true if the named property is defined as a const, false
       * otherwise.
       */
      public boolean isConst(String name)
      {
          Slot slot = getSlot(name, 0, SLOT_QUERY);
          if (slot == null) {
              return false;
          }
          return (slot.getAttributes() & (PERMANENT|READONLY)) ==
                                         (PERMANENT|READONLY);
  
      }
      /**
       * @deprecated Use {@link #getAttributes(String name)}. The engine always
       * ignored the start argument.
       */
      public final int getAttributes(String name, Scriptable start)
      {
          return getAttributes(name);
      }
  
      /**
       * @deprecated Use {@link #getAttributes(int index)}. The engine always
       * ignored the start argument.
       */
      public final int getAttributes(int index, Scriptable start)
      {
          return getAttributes(index);
      }
  
      /**
       * @deprecated Use {@link #setAttributes(String name, int attributes)}.
       * The engine always ignored the start argument.
       */
      public final void setAttributes(String name, Scriptable start,
                                      int attributes)
      {
          setAttributes(name, attributes);
      }
  
      /**
       * @deprecated Use {@link #setAttributes(int index, int attributes)}.
       * The engine always ignored the start argument.
       */
      public void setAttributes(int index, Scriptable start,
                                int attributes)
      {
          setAttributes(index, attributes);
      }
  
      /**
       * Get the attributes of a named property.
       *
       * The property is specified by <code>name</code>
       * as defined for <code>has</code>.<p>
       *
       * @param name the identifier for the property
       * @return the bitset of attributes
       * @exception EvaluatorException if the named property is not found
       * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
       * @see org.mozilla.javascript.ScriptableObject#READONLY
       * @see org.mozilla.javascript.ScriptableObject#DONTENUM
       * @see org.mozilla.javascript.ScriptableObject#PERMANENT
       * @see org.mozilla.javascript.ScriptableObject#EMPTY
       */
      public int getAttributes(String name)
      {
          return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes();
      }
  
      /**
       * Get the attributes of an indexed property.
       *
       * @param index the numeric index for the property
       * @exception EvaluatorException if the named property is not found
       *            is not found
       * @return the bitset of attributes
       * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
       * @see org.mozilla.javascript.ScriptableObject#READONLY
       * @see org.mozilla.javascript.ScriptableObject#DONTENUM
       * @see org.mozilla.javascript.ScriptableObject#PERMANENT
       * @see org.mozilla.javascript.ScriptableObject#EMPTY
       */
      public int getAttributes(int index)
      {
          return findAttributeSlot(null, index, SLOT_QUERY).getAttributes();
      }
  
      /**
       * Set the attributes of a named property.
       *
       * The property is specified by <code>name</code>
       * as defined for <code>has</code>.<p>
       *
       * The possible attributes are READONLY, DONTENUM,
       * and PERMANENT. Combinations of attributes
       * are expressed by the bitwise OR of attributes.
       * EMPTY is the state of no attributes set. Any unused
       * bits are reserved for future use.
       *
       * @param name the name of the property
       * @param attributes the bitset of attributes
       * @exception EvaluatorException if the named property is not found
       * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
       * @see org.mozilla.javascript.ScriptableObject#READONLY
       * @see org.mozilla.javascript.ScriptableObject#DONTENUM
       * @see org.mozilla.javascript.ScriptableObject#PERMANENT
       * @see org.mozilla.javascript.ScriptableObject#EMPTY
       */
      public void setAttributes(String name, int attributes)
      {
          checkNotSealed(name, 0);
          findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes);
      }
  
      /**
       * Set the attributes of an indexed property.
       *
       * @param index the numeric index for the property
       * @param attributes the bitset of attributes
       * @exception EvaluatorException if the named property is not found
       * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
       * @see org.mozilla.javascript.ScriptableObject#READONLY
       * @see org.mozilla.javascript.ScriptableObject#DONTENUM
       * @see org.mozilla.javascript.ScriptableObject#PERMANENT
       * @see org.mozilla.javascript.ScriptableObject#EMPTY
       */
      public void setAttributes(int index, int attributes)
      {
          checkNotSealed(null, index);
          findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes);
      }
  
      /**
       * XXX: write docs.
       */
      public void setGetterOrSetter(String name, int index,
                                    Callable getterOrSetter, boolean isSetter)
      {
          if (name != null && index != 0)
              throw new IllegalArgumentException(name);
  
          checkNotSealed(name, index);
          GetterSlot gslot = (GetterSlot)getSlot(name, index,
                                                 SLOT_MODIFY_GETTER_SETTER);
          gslot.checkNotReadonly();
          if (isSetter) {
              gslot.setter = getterOrSetter;
          } else {
              gslot.getter = getterOrSetter;
          }
          gslot.value = Undefined.instance;
      }
      
      /**
       * Get the getter or setter for a given property. Used by __lookupGetter__
       * and __lookupSetter__.
       * 
       * @param name Name of the object. If nonnull, index must be 0.
       * @param index Index of the object. If nonzero, name must be null.
       * @param isSetter If true, return the setter, otherwise return the getter.
       * @exception IllegalArgumentException if both name and index are nonnull
       *            and nonzero respectively.
       * @return Null if the property does not exist. Otherwise returns either 
       *         the getter or the setter for the property, depending on 
       *         the value of isSetter (may be undefined if unset).
       */
      public Object getGetterOrSetter(String name, int index, boolean isSetter)
      {
          if (name != null && index != 0)
              throw new IllegalArgumentException(name);
          Slot slot = getSlot(name, index, SLOT_QUERY);
          if (slot == null)
              return null;
          if (slot instanceof GetterSlot) {
              GetterSlot gslot = (GetterSlot)slot;
              Object result = isSetter ? gslot.setter : gslot.getter;
              return result != null ? result : Undefined.instance;
          } else
              return Undefined.instance;
      }
  
      /**
       * Returns whether a property is a getter or a setter
       * @param name property name
       * @param index property index
       * @param setter true to check for a setter, false for a getter
       * @return whether the property is a getter or a setter
       */
      protected boolean isGetterOrSetter(String name, int index, boolean setter) {
          Slot slot = getSlot(name, index, SLOT_QUERY);
          if (slot instanceof GetterSlot) {
              if (setter && ((GetterSlot)slot).setter != null) return true;
              if (!setter && ((GetterSlot)slot).getter != null) return true;
          }
          return false;
      }
  
      void addLazilyInitializedValue(String name, int index,
                                     LazilyLoadedCtor init, int attributes)
      {
          if (name != null && index != 0)
              throw new IllegalArgumentException(name);
          checkNotSealed(name, index);
          GetterSlot gslot = (GetterSlot)getSlot(name, index,
                                                 SLOT_MODIFY_GETTER_SETTER);
          gslot.setAttributes(attributes);
          gslot.getter = null;
          gslot.setter = null;
          gslot.value = init;
      }
  
      /**
       * Returns the prototype of the object.
       */
      public Scriptable getPrototype()
      {
          return prototypeObject;
      }
  
      /**
       * Sets the prototype of the object.
       */
      public void setPrototype(Scriptable m)
      {
          prototypeObject = m;
      }
  
      /**
       * Returns the parent (enclosing) scope of the object.
       */
      public Scriptable getParentScope()
      {
          return parentScopeObject;
      }
  
      /**
       * Sets the parent (enclosing) scope of the object.
       */
      public void setParentScope(Scriptable m)
      {
          parentScopeObject = m;
      }
  
      /**
       * Returns an array of ids for the properties of the object.
       *
       * <p>Any properties with the attribute DONTENUM are not listed. <p>
       *
       * @return an array of java.lang.Objects with an entry for every
       * listed property. Properties accessed via an integer index will
       * have a corresponding
       * Integer entry in the returned array. Properties accessed by
       * a String will have a String entry in the returned array.
       */
      public Object[] getIds() {
          return getIds(false);
      }
  
      /**
       * Returns an array of ids for the properties of the object.
       *
       * <p>All properties, even those with attribute DONTENUM, are listed. <p>
       *
       * @return an array of java.lang.Objects with an entry for every
       * listed property. Properties accessed via an integer index will
       * have a corresponding
       * Integer entry in the returned array. Properties accessed by
       * a String will have a String entry in the returned array.
       */
      public Object[] getAllIds() {
          return getIds(true);
      }
  
      /**
       * Implements the [[DefaultValue]] internal method.
       *
       * <p>Note that the toPrimitive conversion is a no-op for
       * every type other than Object, for which [[DefaultValue]]
       * is called. See ECMA 9.1.<p>
       *
       * A <code>hint</code> of null means "no hint".
       *
       * @param typeHint the type hint
       * @return the default value for the object
       *
       * See ECMA 8.6.2.6.
       */
      public Object getDefaultValue(Class<?> typeHint)
      {
          return getDefaultValue(this, typeHint);
      }
      
      public static Object getDefaultValue(Scriptable object, Class<?> typeHint)
      {
          Context cx = null;
          for (int i=0; i < 2; i++) {
              boolean tryToString;
              if (typeHint == ScriptRuntime.StringClass) {
  