/*
 * The contents of this file are subject to the Netscape 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/NPL/

 * 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 Netscape are
 * Copyright (C) 1997-1999 Netscape Communications Corporation. All
 * Rights Reserved.

 * Contributor(s): 

 * Alternatively, the contents of this file may be used under the
 * terms of the GNU Public License (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 NPL, indicate your decision by
 * deleting the provisions above and replace 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 NPL or the GPL.
 */
/* Header file */
#include <string.h>
#include "js-utils.h"
#include "js-scriptruntime.h"
#include "js-ctorfunction.h"
#include "js-hostctorfunction.h"
#include "js-hostmethodfunction.h"
#include "js-nativeobject.h"
#include "js-nativeglobal.h"
#include "js-nativefunction.h"
#include "js-nativehost.h"
#include "js-propertyexception.h"
#include "js-runtimeexception.h"
#include "js-exceptionhandler.h"
#ifdef REGEXP
#include "js-nativeregexp.h"
#endif
#include "js-scriptableobject.h"

/* Internal data */
const jsString_t JSScriptableObject::className = "ScriptableObject";
const JSScriptableObject::Slot * JSScriptableObject::REMOVED = (Slot *) - 1;

/* Procedures */
int
 JSScriptableObject::getHashCode(jsString_t id)
{
#ifdef FASTER
    const int M = 31;
    int value;

    value = 0;
    for (int i = 0; id[i] != '\0'; ++i) {
        value *= M;
        value += id[i];
    }
    return value & 0x7fffffff;
#else /* !FATSER */
    union {
        int index;
        uint8_t string[sizeof(int)];
    } sti;

    sti.index = 0;

    for (int i = 0; i < (int)sizeof(int); i++) {
        sti.string[i] = id[i];
        if (id[i] == 0)
            break;
    }
    return sti.index & 0x7fffffff;
#endif /* FASTER */
}
int JSScriptableObject::getSlot(jsString_t id, int index)
{
#ifdef COMPACT_MEM
    if (slotsSet == NULL) {
        return SLOT_NOT_FOUND;
    }
    Slot **slots = slotsSet->slots;
    int slotsLength = slotsSet->slotsLength;
#endif
    if (slots == NULL) {
        return SLOT_NOT_FOUND;
    }

    int start = (index & 0x7fffffff) % slotsLength;
    int i = start;
    do {
        Slot *slot = slots[i];

        if (slot == NULL)
            return SLOT_NOT_FOUND;
        if (slot != REMOVED) {
            if (slot->intKey == index && (slot->stringKey == id || (id != NULL && strcmp(id, slot->stringKey) == 0))) {
                return i;
            }
        }
        if (++i == slotsLength) {
            i = 0;
        }
    } while (i != start);
    return SLOT_NOT_FOUND;
}
int JSScriptableObject::getSlotToSet(JSContext * cx, jsString_t id, int index, jsBool_t getterSlot)
{
#ifdef COMPACT_MEM
    if (slotsSet == NULL) {
        slotsSet = (SlotsSet *) JS_NewGCObj(cx, sizeof(*slotsSet));
        memset(slotsSet, 0, sizeof(*slotsSet));
    }
    if (slotsSet->slotsLength == 0) {
        slotsSet->slots = (Slot **) JS_NewGCObj(cx, sizeof(*slotsSet->slots) * 5);
        memset(slotsSet->slots, 0, sizeof(*slotsSet->slots) * 5);
        slotsSet->slotsLength = 5;
    }
    Slot **slots = slotsSet->slots;
    int slotsLength = slotsSet->slotsLength;
#else /* !COMPACT_MEM */
    if (slotsLength == 0) {
        slots = (Slot **) JS_NewGCObj(cx, sizeof(*slots) * 5);
        memset(slots, 0, sizeof(*slots) * 5);
        slotsLength = 5;
    }
#endif /* COMPACT_MEM */
    int start = (index & 0x7fffffff) % slotsLength;
    jsBool_t sawRemoved = FALSE;
    int i = start;

    do {
        Slot *slot = slots[i];

        if (slot == NULL) {
            return addSlot(cx, id, index, getterSlot);
        }
        if (slot != REMOVED) {
            if (slot->intKey == index && (slot->stringKey == id || (id != NULL && strcmp(id, slot->stringKey) == 0))) {
                return i;
            }
        }
        else {
            sawRemoved = TRUE;
        }
        if (++i == slotsLength) {
            i = 0;
        }
    } while (i != start);
    if (sawRemoved) {
        return addSlot(cx, id, index, getterSlot);
    }
    cx->exceptionHandler->throwEx(new(cx) JSRuntimeException("Hashtable internal error"));
    /* NOT REACHED */
    return SLOT_NOT_FOUND;
}
int JSScriptableObject::addSlot(JSContext * cx, jsString_t id, int index, jsBool_t getterSlot)
{
#ifdef COMPACT_MEM
    Slot **slots = slotsSet->slots;
    int slotsLength = slotsSet->slotsLength;
#endif
    int start = (index & 0x7fffffff) % slotsLength;
    int i = start;
    do {
        Slot *slot = slots[i];

        if (slot == NULL || slot == REMOVED) {
#ifdef COMPACT_MEM
            int count = ++slotsSet->count;

            if (count >= slotsLength) {
                --slotsSet->count;
#else
            count++;
            if ((4 * count) > (3 * slotsLength)) {
                --count;
#endif
                grow(cx);
                return getSlotToSet(cx, id, index, getterSlot);
            }
            slot = getterSlot ? new(cx) GetterSlot() : new(cx) Slot();
            slot->stringKey = id != NULL ? JS_NewGCString(cx, id)
                    : (jsString_t) NULL;
            slot->intKey = index;
            slot->flags = 0;
            slot->attributes = 0;
            slots[i] = slot;
            return i;
        }
        if (slot->intKey == index && (slot->stringKey == id || (id != NULL && strcmp(id, slot->stringKey) == 0))) {
            return i;
        }
        if (++i == slotsLength) {
            i = 0;
        }
    } while (i != start);
    cx->exceptionHandler->throwEx(new(cx) JSRuntimeException("Hashtable internal error"));
    /* NOT REACHED */
    return SLOT_NOT_FOUND;
}
void JSScriptableObject::removeSlot(JSContext * cx, jsString_t id, int index)
{
    int slotIndex = getSlot(id, index);

    if (slotIndex == SLOT_NOT_FOUND) {
        return;
    }
#ifdef COMPACT_MEM
    Slot **slots = slotsSet->slots;
#endif
    if ((slots[slotIndex]->attributes & JS_PA_Permanent) != 0) {
        return;
    }
    slots[slotIndex] = (Slot *) REMOVED;
#ifdef COMPACT_MEM
    slotsSet->count--;
#else
    count--;
#endif
}

void JSScriptableObject::grow(JSContext * cx)
{
#ifdef COMPACT_MEM
    Slot **slots = slotsSet->slots;
    int slotsLength = slotsSet->slotsLength;
#endif
    //20040623 ichino
#if 0
    int newSlotsLength = slotsLength * 2 + 1;
#else
    int newSlotsLength;
    if (slotsLength > 256) {
        newSlotsLength = slotsLength + 256;
    }
    else {
        newSlotsLength = slotsLength * 2 + 1;
    }
#endif

    Slot **newSlots = (Slot **) JS_NewGCObj(cx,
            (sizeof(*slots) * newSlotsLength));

    memset(newSlots, 0, sizeof(*newSlots) * newSlotsLength);
    for (int j = slotsLength - 1; j >= 0; j--) {
        Slot *slot = slots[j];

        if (slot == NULL || slot == REMOVED) {
            continue;
        }
        int k = (slot->intKey & 0x7fffffff) % newSlotsLength;
        while (newSlots[k] != NULL) {
            if (++k == newSlotsLength) {
                k = 0;
            }
        }
        newSlots[k] = slot;
    }
#ifdef COMPACT_MEM
    slotsSet->slots = newSlots;
    slotsSet->slotsLength = newSlotsLength;
#else
    slots = newSlots;
    slotsLength = newSlotsLength;
#endif
}
jsVal_t *JSScriptableObject::getIds(JSContext * cx, int *numIds, jsBool_t getAll)
{
#ifdef COMPACT_MEM
    if (slotsSet == NULL) {
        *numIds = 0;
        return NULL;
    }
    Slot **slots = slotsSet->slots;
    int slotsLength = slotsSet->slotsLength;
#endif
    if (slots == NULL) {
        *numIds = 0;
        return NULL;
    }
    int count = 0;
    int i;  // avoid compile error on vxWorks

    for (i = slotsLength - 1; i >= 0; i--) {
        Slot *slot = slots[i];

        if (slot == NULL || slot == REMOVED) {
            continue;
        }
        if (getAll || (slot->attributes & JS_PA_DontEnum) == 0) {
            count++;
        }
    }
    jsVal_t *result;

    result = (jsVal_t *) JS_NewZoneObj(cx, count * sizeof(*result));
    *numIds = count;
    for (i = slotsLength - 1; i >= 0; i--) {
        Slot *slot = slots[i];

        if (slot == NULL || slot == REMOVED) {
            continue;
        }
        if (getAll || (slot->attributes & JS_PA_DontEnum) == 0) {
            jsVal_t val;

            if (slot->stringKey != NULL) {
                val = JS_STRING_TO_VAL(slot->stringKey);
            }
            else {
                val = JSScriptRuntime::toValue(cx, slot->intKey);
            }
            result[count - 1] = val;
            count -= 1;
        }
    }
    return result;
}

JSFunction *JSScriptableObject::getFunctionProperty(JSContext * cx, JSFlattenedObject * f, jsString_t name)
{
    jsVal_t val;

    jsString_t s = JS_NewZoneString(cx, name);  // align string

    val = f->getProperty(cx, JS_STRING_TO_VAL(s));
    JS_DeleteZoneObj(cx, s);

    if (JS_VAL_IS_OBJECT(val) && val != JS_VAL_NULL) {
        JSScriptable *scriptable;

        scriptable = JS_VAL_TO_OBJECT(val);
        if (scriptable->isInstanceOf(JSNativeFunction::className)) {
            return (JSNativeFunction *) scriptable;
        }
        if (scriptable->isInstanceOf(JSNativeHost::className)) {
            return (JSNativeHost *) scriptable;
        }
#ifdef REGEXP
        if (scriptable->isInstanceOf(JSNativeRegExp::className)) {
            return (JSNativeRegExp *) scriptable;
        }
#endif
    }
    return NULL;
}

void JSScriptableObject::defineClass(JSContext * cx, JSScriptable * scope, JSScriptable * proto, jsConstructor_t constructor, jsFinishInit_t finishInit)
{
    JSCtorFunction *ctor;

    proto->setPrototype(cx, getObjectPrototype(cx, scope));
    if (constructor != NULL) {
        jsString_t className = proto->getClassName(cx);

        ctor = new(cx) JSCtorFunction(cx, className, className, constructor, scope);
        ctor->addAsConstructor(cx, scope, proto);
    }
    else {
        ctor = NULL;
    }
    if (finishInit != NULL) {
        (*finishInit) (cx, scope, ctor, proto);
    }
}
void JSScriptableObject::defineClass(JSContext * cx, JSScriptable * scope, JSScriptable * proto,::jsConstructor_t constructor)
{
    JSHostCtorFunction *ctor;

    proto->setPrototype(cx, getObjectPrototype(cx, scope));
    if (constructor != NULL) {
        jsString_t className = proto->getClassName(cx);

        ctor = new(cx) JSHostCtorFunction(cx, className, className, constructor, scope);
        ctor->addAsConstructor(cx, scope, proto);
    }
    else {
        ctor = NULL;
    }
}
JSScriptable *JSScriptableObject::getClassPrototype(JSContext * cx, JSScriptable * scope, jsString_t className)
{
    scope = getTopLevelScope(cx, scope);

    jsVal_t ctor;
    jsBool_t ok = JSScriptRuntime::getTopLevelProp(cx, scope, className, &ctor);

    if (!ok || !JS_VAL_IS_OBJECT(ctor) || ctor == JS_VAL_NULL) {
        return NULL;
    }
    JSScriptable *ctorObj = JS_VAL_TO_OBJECT(ctor);

    if (!ctorObj->has(cx, "prototype", ctorObj)) {
        return NULL;
    }
    jsVal_t proto;

    ok = ctorObj->get(cx, "prototype", ctorObj, &proto);
    if (!ok || !JS_VAL_IS_OBJECT(proto) || proto == JS_VAL_NULL) {
        return NULL;
    }
    return JS_VAL_TO_OBJECT(proto);
}

/**
  * Get the Function.prototype property.
  * See ECMA 15.3.4.
 */
JSScriptable *JSScriptableObject::getFunctionPrototype(JSContext * cx, JSScriptable * scope)
{
    return getClassPrototype(cx, scope, JSNativeFunction::className);
}

/**
 * Get the Object.prototype property.
 * See ECMA 15.2.4.
 */
JSScriptable *JSScriptableObject::getObjectPrototype(JSContext * cx, JSScriptable * scope)
{
    return getClassPrototype(cx, scope, JSNativeObject::className);
}

JSScriptable *JSScriptableObject::getTopLevelScope(JSContext * cx, JSScriptable * obj)
{
    JSScriptable *next = obj;

    do {
        obj = next;
        next = obj->getParentScope(cx);
    } while (next != NULL);
    return obj;
}
void JSScriptableObject::defineFunctionProperties(JSContext * cx, jsString_t clazz, int numFunctions, jsFunction_t * functions, int attributes)
{
    for (int i = 0; i < numFunctions; ++i) {
        JSMethodFunction *function = new(cx) JSMethodFunction(cx, functions[i].name, clazz,
                functions[i].cfunc, this);

        if (functions[i].length > 0) {
            function->setParmsLength(functions[i].length);
        }
        if (functions[i].length >= 0) {
            function->setLength(functions[i].length);
        }
        defineProperty(cx, functions[i].name, JS_OBJECT_TO_VAL(function), attributes);
    }
}
void JSScriptableObject::defineFunctionProperties(JSContext * cx, jsString_t clazz, int numFunctions,::jsFunction_t * functions)
{
    for (int i = 0; i < numFunctions; ++i) {
        JSHostMethodFunction *function = new(cx) JSHostMethodFunction(cx, functions[i].name, NULL,
                functions[i].cfunc, this);

        defineProperty(cx, functions[i].name, JS_OBJECT_TO_VAL(function), functions[i].attributes);
    }
}
void JSScriptableObject::defineProperty(JSContext * cx, jsString_t name, jsVal_t value, int attributes)
{
    put(cx, name, this, value);
    setAttributes(cx, name, this, attributes);
}
void JSScriptableObject::defineProperty(JSContext * cx, jsString_t name, jsGetterProc_t getter, jsSetterProc_t setter, int attributes)
{
    int slotIndex = getSlotToSet(cx, name, getHashCode(name), TRUE);
#ifdef COMPACT_MEM
    GetterSlot *slot = (GetterSlot *) slotsSet->slots[slotIndex];
#else
    GetterSlot *slot = (GetterSlot *) slots[slotIndex];
#endif

    slot->getter = getter;
    slot->setter = setter;
    slot->value = JS_VAL_NULL;
    slot->attributes = (short)attributes;
    slot->flags = getter != NULL ? Slot::HAS_GETTER : 0;
    slot->flags |= setter != NULL ? Slot::HAS_SETTER : 0;
}
void JSScriptableObject::defineProperties(JSContext * cx, int numProperties, jsProperty_t * properties)
{
    for (int i = 0; i < numProperties; ++i) {
        if (properties[i].getter == NULL && properties[i].setter == NULL) {
            defineProperty(cx, properties[i].name, JS_VAL_NULL, properties[i].attributes);
        }
        else {
            defineProperty(cx, properties[i].name, properties[i].getter, properties[i].setter, properties[i].attributes);
        }
    }
}
int JSScriptableObject::getAttributes(JSContext * cx, jsString_t name, JSScriptable * start)
{
    int slotIndex = getSlot(name, getHashCode(name));

    if (slotIndex != SLOT_NOT_FOUND) {
        cx->exceptionHandler->throwEx(new(cx) JSPropertyException("msg.prop.not.found"));
        /* NOT REACHED */
    }
#ifdef COMPACT_MEM
    return slotsSet->slots[slotIndex]->attributes;
#else
    return slots[slotIndex]->attributes;
#endif
}
int JSScriptableObject::getAttributes(JSContext * cx, int index, JSScriptable * start)
{
    int slotIndex = getSlot(NULL, index);

    if (slotIndex != SLOT_NOT_FOUND) {
        cx->exceptionHandler->throwEx(new(cx) JSPropertyException(JSContext::getMessage(cx, "msg.prop.not.found")));
        /* NOT REACHED */
    }
#ifdef COMPACT_MEM
    return slotsSet->slots[slotIndex]->attributes;
#else
    return slots[slotIndex]->attributes;
#endif
}
void JSScriptableObject::setAttributes(JSContext * cx, jsString_t name, JSScriptable * start, int attributes)
{
    const int mask = JS_PA_ReadOnly | JS_PA_DontEnum | JS_PA_Permanent;

    attributes &= mask; // mask out unused bits

    int slotIndex = getSlot(name, getHashCode(name));

    if (slotIndex == SLOT_NOT_FOUND) {
        cx->exceptionHandler->throwEx(new(cx) JSPropertyException(JSContext::getMessage(cx, "msg.prop.not.found")));
        /* NOT REACHED */
    }
#ifdef COMPACT_MEM
    slotsSet->slots[slotIndex]->attributes = (short)attributes;
#else
    slots[slotIndex]->attributes = (short)attributes;
#endif
}
void JSScriptableObject::setAttributes(JSContext * cx, int index, JSScriptable * start, int attributes)
{
    const int mask = JS_PA_ReadOnly | JS_PA_DontEnum | JS_PA_Permanent;

    attributes &= mask; // mask out unused bits

    int slotIndex = getSlot(NULL, index);

    if (slotIndex == SLOT_NOT_FOUND) {
        cx->exceptionHandler->throwEx(new(cx) JSPropertyException(JSContext::getMessage(cx, "msg.prop.not.found")));
        /* NOT REACHED */
    }
#ifdef COMPACT_MEM
    slotsSet->slots[slotIndex]->attributes = (short)attributes;
#else
    slots[slotIndex]->attributes = (short)attributes;
#endif
}
jsBool_t JSScriptableObject::isInstanceOf(const jsString_t className)
{
    return className == JSScriptableObject::className;
}

jsBool_t JSScriptableObject::has(JSContext * cx, jsString_t name, JSScriptable * start)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    jsBool_t result = getSlot(name, getHashCode(name)) != SLOT_NOT_FOUND;
#ifdef THREADS
    cx->runtime->unlock();
#endif
    return result;
}

jsBool_t JSScriptableObject::has(JSContext * cx, int index, JSScriptable * start)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    jsBool_t result = getSlot(NULL, index) != SLOT_NOT_FOUND;
#ifdef THREADS
    cx->runtime->unlock();
#endif
    return result;
}

jsBool_t JSScriptableObject::get(JSContext * cx, jsString_t name, JSScriptable * start, jsVal_t * value)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    int hashCode = getHashCode(name);

#ifdef COMPACT_MEM
    if (slotsSet != NULL) {
        if (hashCode == slotsSet->lastHash) {
            if (slotsSet->lastName != NULL && strcmp(name, slotsSet->lastName) == 0) {
                *value = slotsSet->lastValue;
#ifdef THREADS
                cx->runtime->unlock();
#endif
                return TRUE;
            }
        }
    }
#else /* !COMPACT_MEM */
    if (hashCode == lastHash) {
        if (lastName != NULL && strcmp(name, lastName) == 0) {
            *value = lastValue;
#ifdef THREADS
            cx->runtime->unlock();
#endif
            return TRUE;
        }
    }
#endif /* COMPACT_MEM */

    int slotIndex = getSlot(name, hashCode);

    if (slotIndex == SLOT_NOT_FOUND) {
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return FALSE;
    }
#ifdef COMPACT_MEM
    Slot *slot = slotsSet->slots[slotIndex];
#else
    Slot *slot = slots[slotIndex];
#endif
    if ((slot->flags & Slot::HAS_GETTER) != 0) {
        GetterSlot *getterSlot = (GetterSlot *) slot;
        jsBool_t result = getterSlot->getter((jsContext_t) cx, (jsObject_t) this,
                name, (jsObject_t) start, value);
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return result;
    }
#ifdef COMPACT_MEM
    slotsSet->lastName = slot->stringKey;
    slotsSet->lastHash = hashCode;
    slotsSet->lastValue = slot->value;
    *value = slotsSet->lastValue;
#else
    lastName = slot->stringKey;
    lastHash = hashCode;
    lastValue = slot->value;
    *value = lastValue;
#endif
#ifdef THREADS
    cx->runtime->unlock();
#endif
    return TRUE;
}

jsBool_t JSScriptableObject::get(JSContext * cx, int index, JSScriptable * start, jsVal_t * value)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    int slotIndex = getSlot(NULL, index);

    if (slotIndex == SLOT_NOT_FOUND) {
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return FALSE;
    }
#ifdef COMPACT_MEM
    *value = slotsSet->slots[slotIndex]->value;
#else
    *value = slots[slotIndex]->value;
#endif

#ifdef THREADS
    cx->runtime->unlock();
#endif
    return TRUE;
}

void JSScriptableObject::put(JSContext * cx, jsString_t name, JSScriptable * start, jsVal_t value)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    int hashCode = getHashCode(name);

    int slotIndex = getSlot(name, hashCode);

    if (slotIndex == SLOT_NOT_FOUND) {
        if (start != this) {
            start->put(cx, name, start, value);
#ifdef THREADS
            cx->runtime->unlock();
#endif
            return;
        }
        slotIndex = getSlotToSet(cx, name, hashCode, FALSE);
    }

#ifdef COMPACT_MEM
    Slot *slot = slotsSet->slots[slotIndex];
#else
    Slot *slot = slots[slotIndex];
#endif

    if ((slot->attributes & JS_PA_ReadOnly) != 0) {
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return;
    }
    if ((slot->flags & Slot::HAS_SETTER) != 0) {
        GetterSlot *getterSlot = (GetterSlot *) slot;

        getterSlot->setter((jsContext_t) cx, (jsObject_t) this, name, (jsObject_t) start, value);
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return;
    }

    if (this == start) {
        slot->value = value;
#ifdef COMPACT_MEM
        if (hashCode == slotsSet->lastHash) {
            if (slotsSet->lastName != NULL && strcmp(name, slotsSet->lastName) == 0) {
                slotsSet->lastValue = value;
            }
        }
#else
        if (hashCode == lastHash) {
            if (lastName != NULL && strcmp(name, lastName) == 0) {
                lastValue = value;
            }
        }
#endif
    }
    else {
        start->put(cx, name, start, value);
    }
#ifdef THREADS
    cx->runtime->unlock();
#endif
}
void JSScriptableObject::put(JSContext * cx, int index, JSScriptable * start, jsVal_t value)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    int slotIndex = getSlot(NULL, index);

    if (slotIndex == SLOT_NOT_FOUND) {
        if (start != this) {
            start->put(cx, index, start, value);
#ifdef THREADS
            cx->runtime->unlock();
#endif
            return;
        }
        slotIndex = getSlotToSet(cx, NULL, index, false);
    }

#ifdef COMPACT_MEM
    Slot *slot = slotsSet->slots[slotIndex];
#else
    Slot *slot = slots[slotIndex];
#endif

    if ((slot->attributes & JS_PA_ReadOnly) != 0) {
#ifdef THREADS
        cx->runtime->unlock();
#endif
        return;
    }
    if (this == start) {
        slot->value = value;
    }
    else {
        start->put(cx, index, start, value);
    }
#ifdef THREADS
    cx->runtime->unlock();
#endif
}

void JSScriptableObject::del(JSContext * cx, jsString_t name)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    int hashCode = getHashCode(name);

#ifdef COMPACT_MEM
    if (slotsSet->lastHash == hashCode) {
        if (slotsSet->lastName != NULL && strcmp(name, slotsSet->lastName) == 0) {
            slotsSet->lastName = NULL;
        }
    }
#else
    if (lastHash == hashCode) {
        if (lastName != NULL && strcmp(name, lastName) == 0) {
            lastName = NULL;
        }
    }
#endif
    removeSlot(cx, name, hashCode);
#ifdef THREADS
    cx->runtime->unlock();
#endif
}
void JSScriptableObject::del(JSContext * cx, int index)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    removeSlot(cx, NULL, index);
#ifdef THREADS
    cx->runtime->unlock();
#endif
}

JSScriptable *JSScriptableObject::getPrototype(JSContext * cx)
{
    return prototype;
}

void JSScriptableObject::setPrototype(JSContext * cx, JSScriptable * prototype)
{
    this->prototype = prototype;
}

JSScriptable *JSScriptableObject::getParentScope(JSContext * cx)
{
    return parent;
}

void JSScriptableObject::setParentScope(JSContext * cx, JSScriptable * parent)
{
    this->parent = parent;
}
jsVal_t *JSScriptableObject::getIds(JSContext * cx, int *numIds)
{
#ifdef THREADS
    cx->runtime->lock();
#endif
    jsVal_t *ids = getIds(cx, numIds, FALSE);
#ifdef THREADS
    cx->runtime->unlock();
#endif
    return ids;
}

/**
 * Implements the [[DefaultValue]] internal method.
 *   
 * Note that the toPrimitive conversion is a no-op for
 * every type other than Object, for which [[DefaultValue]]
 * is called. See ECMA 9.1.
 *   
 * A hint of null means "no hint".
 *   
 * See ECMA 8.6.2.6.
 */
jsVal_t JSScriptableObject::getDefaultValue(JSContext * cx, jsType_t typeHint)
{
    JSFlattenedObject f(this);
    jsVal_t val;
    JSException *ex;

    if ((ex = enterTry(cx)) == NULL) {
        int i;

        for (i = 0; i < 2; i++) {
            if (typeHint == JS_T_String ? i == 0 : i == 1) {
                JSFunction *fun = getFunctionProperty(cx, &f, "toString");

                if (fun == NULL) {
                    continue;
                }
                val = fun->call(cx, ((JSNativeFunction *) fun)->getParentScope(cx), f.getObject(), 0, NULL);
            }
            else {
                jsString_t hint;

                switch (typeHint) {
                case JS_T_Null:
                    hint = (jsString_t) "undefined";
                    break;
                case JS_T_String:
                    hint = (jsString_t) "string";
                    break;
                case JS_T_Object:
                    hint = (jsString_t) "object";
                    break;
                case JS_T_Function:
                    hint = (jsString_t) "function";
                    break;
                case JS_T_Boolean:
                    hint = (jsString_t) "boolean";
                    break;
                default:
                    hint = (jsString_t) "number";
                    break;
                }
                JSFunction *fun = getFunctionProperty(cx, &f, "valueOf");

                if (fun == NULL) {
                    continue;
                }
                jsVal_t args[1];
                double s[2];    // use double to align string

                strcpy((jsChar_t *) s, hint);
                args[0] = JS_STRING_TO_VAL((jsChar_t *) s);
                val = fun->call(cx, ((JSNativeFunction *) fun)->getParentScope(cx), this, 1, args);
            }
            if (val != JS_VAL_NULL && (val == JS_VAL_VOID || !JS_VAL_IS_OBJECT(val)
                            || typeHint == JS_T_Object || typeHint == JS_T_Function)) {
                cx->exceptionHandler->exitTry();
                return val;
            }
        }
        cx->exceptionHandler->exitTry();
    }
    cx->exceptionHandler->throwEx(JSNativeGlobal::constructError(cx, "TypeError", JSScriptRuntime::getMessage(cx, "msg.default.value"), this));
    /* NOT REACHED */
    return JS_VAL_VOID;
}

jsBool_t JSScriptableObject::hasInstance(JSContext * cx, JSScriptable * instance)
{
    return JSScriptRuntime::jsDelegatesTo(cx, instance, this);
}

void JSScriptableObject::mark(JSContext * cx)
{
    JSGCHeap *gcHeap = &cx->runtime->gcHeap;

    if (gcHeap->isMarkObj(this))
        return;

    JSScriptable::mark(cx);

    if (parent != NULL)
        parent->mark(cx);
    if (prototype != NULL)
        prototype->mark(cx);

#ifdef COMPACT_MEM
    if (slotsSet != NULL) {
        gcHeap->markObj(slotsSet);

        Slot **slots = slotsSet->slots;
        int slotsLength = slotsSet->slotsLength;

        if (slots != NULL) {
            gcHeap->markObj(slots);
            for (int i = 0; i < slotsLength; ++i) {
                Slot *slot;

                slot = slots[i];
                if (slot != NULL && slot != REMOVED) {
                    gcHeap->markObj(slot);
                    if (slot->stringKey != NULL) {
                        gcHeap->markObj(slot->stringKey);
                    }
                    JS_MarkVal(cx, slot->value);
                }
            }
        }
    }
#else /* !COMPACT_MEM */
    if (slots != NULL) {
        gcHeap->markObj(slots);
        for (int i = 0; i < slotsLength; ++i) {
            Slot *slot;

            slot = slots[i];
            if (slot != NULL && slot != REMOVED) {
                gcHeap->markObj(slot);
                if (slot->stringKey != NULL) {
                    gcHeap->markObj(slot->stringKey);
                }
                JS_MarkVal(cx, slot->value);
            }
        }
    }
#endif /* COMPACT_MEM */
}

//2003.10.08 ichino
void JSScriptableObject::removeAllProperties()
{
    slots = NULL;
    slotsLength = 0;
    count = 0;
    lastName = NULL;
    lastHash = 0;
}

JSScriptableObject::JSScriptableObject()
{
    parent = NULL;
    prototype = NULL;
#ifdef COMPACT_MEM
    slotsSet = NULL;
#else
    slots = NULL;
    slotsLength = 0;
    count = 0;
    lastName = NULL;
    lastHash = 0;   //avoid purify UMR error

#endif
}
