/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * 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 Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifndef jsscopeinlines_h___
#define jsscopeinlines_h___

#include <new>
#include "jsbool.h"
#include "jscntxt.h"
#include "jsdbgapi.h"
#include "jsfun.h"
#include "jsobj.h"
#include "jsscope.h"

#include "jscntxtinlines.h"

inline void
js::Shape::freeTable(JSContext *cx)
{
    if (table) {
        cx->destroy(table);
        table = NULL;
    }
}

inline js::EmptyShape *
JSObject::getEmptyShape(JSContext *cx, js::Class *aclasp,
                        /* gc::FinalizeKind */ unsigned kind)
{
    JS_ASSERT(kind >= js::gc::FINALIZE_OBJECT0 && kind <= js::gc::FINALIZE_OBJECT_LAST);
    int i = kind - js::gc::FINALIZE_OBJECT0;

    if (!emptyShapes) {
        emptyShapes = (js::EmptyShape**)
            cx->calloc(sizeof(js::EmptyShape*) * js::gc::JS_FINALIZE_OBJECT_LIMIT);
        if (!emptyShapes)
            return NULL;

        /*
         * Always fill in emptyShapes[0], so canProvideEmptyShape works.
         * Other empty shapes are filled in lazily.
         */
        emptyShapes[0] = js::EmptyShape::create(cx, aclasp);
        if (!emptyShapes[0]) {
            cx->free(emptyShapes);
            emptyShapes = NULL;
            return NULL;
        }
    }

    JS_ASSERT(aclasp == emptyShapes[0]->getClass());

    if (!emptyShapes[i]) {
        emptyShapes[i] = js::EmptyShape::create(cx, aclasp);
        if (!emptyShapes[i])
            return NULL;
    }

    return emptyShapes[i];
}

inline bool
JSObject::canProvideEmptyShape(js::Class *aclasp)
{
    return !emptyShapes || emptyShapes[0]->getClass() == aclasp;
}

inline void
JSObject::updateShape(JSContext *cx)
{
    JS_ASSERT(isNative());
    js::LeaveTraceIfGlobalObject(cx, this);
    if (hasOwnShape())
        setOwnShape(js_GenerateShape(cx, false));
    else
        objShape = lastProp->shape;
}

inline void
JSObject::updateFlags(const js::Shape *shape, bool isDefinitelyAtom)
{
    jsuint index;
    if (!isDefinitelyAtom && js_IdIsIndex(shape->id, &index))
        setIndexed();

    if (shape->isMethod())
        setMethodBarrier();
}

inline void
JSObject::extend(JSContext *cx, const js::Shape *shape, bool isDefinitelyAtom)
{
    setLastProperty(shape);
    updateFlags(shape, isDefinitelyAtom);
    updateShape(cx);
}

inline void
JSObject::trace(JSTracer *trc)
{
    if (!isNative())
        return;

    JSContext *cx = trc->context;
    js::Shape *shape = lastProp;

    if (IS_GC_MARKING_TRACER(trc) && cx->runtime->gcRegenShapes) {
        /*
         * Either this object has its own shape, which must be regenerated, or
         * it must have the same shape as lastProp.
         */
        if (!shape->hasRegenFlag()) {
            shape->shape = js_RegenerateShapeForGC(cx);
            shape->setRegenFlag();
        }

        uint32 newShape = shape->shape;
        if (hasOwnShape()) {
            newShape = js_RegenerateShapeForGC(cx);
            JS_ASSERT(newShape != shape->shape);
        }
        objShape = newShape;
    }

    /* Trace our property tree or dictionary ancestor line. */
    do {
        shape->trace(trc);
    } while ((shape = shape->parent) != NULL);
}

namespace js {

inline
Shape::Shape(jsid id, js::PropertyOp getter, js::PropertyOp setter, uint32 slot, uintN attrs,
             uintN flags, intN shortid, uint32 shape, uint32 slotSpan)
  : JSObjectMap(shape, slotSpan),
    numSearches(0), table(NULL), id(id), rawGetter(getter), rawSetter(setter), slot(slot),
    attrs(uint8(attrs)), flags(uint8(flags)), shortid(int16(shortid)), parent(NULL)
{
    JS_ASSERT_IF(slotSpan != SHAPE_INVALID_SLOT, slotSpan < JSObject::NSLOTS_LIMIT);
    JS_ASSERT_IF(getter && (attrs & JSPROP_GETTER), getterObj->isCallable());
    JS_ASSERT_IF(setter && (attrs & JSPROP_SETTER), setterObj->isCallable());
    kids.setNull();
}

inline
Shape::Shape(JSContext *cx, Class *aclasp)
  : JSObjectMap(js_GenerateShape(cx, false), JSSLOT_FREE(aclasp)), numSearches(0), table(NULL),
    id(JSID_EMPTY), clasp(aclasp), rawSetter(NULL), slot(SHAPE_INVALID_SLOT), attrs(0),
    flags(SHARED_EMPTY), shortid(0), parent(NULL)
{
    kids.setNull();
}

inline JSDHashNumber
Shape::hash() const
{
    JSDHashNumber hash = 0;

    /* Accumulate from least to most random so the low bits are most random. */
    JS_ASSERT_IF(isMethod(), !rawSetter || rawSetter == js_watch_set);
    if (rawGetter)
        hash = JS_ROTATE_LEFT32(hash, 4) ^ jsuword(rawGetter);
    if (rawSetter)
        hash = JS_ROTATE_LEFT32(hash, 4) ^ jsuword(rawSetter);
    hash = JS_ROTATE_LEFT32(hash, 4) ^ (flags & PUBLIC_FLAGS);
    hash = JS_ROTATE_LEFT32(hash, 4) ^ attrs;
    hash = JS_ROTATE_LEFT32(hash, 4) ^ shortid;
    hash = JS_ROTATE_LEFT32(hash, 4) ^ slot;
    hash = JS_ROTATE_LEFT32(hash, 4) ^ JSID_BITS(id);
    return hash;
}

inline bool
Shape::matches(const js::Shape *other) const
{
    JS_ASSERT(!JSID_IS_VOID(id));
    JS_ASSERT(!JSID_IS_VOID(other->id));
    return id == other->id &&
           matchesParamsAfterId(other->rawGetter, other->rawSetter, other->slot, other->attrs,
                                other->flags, other->shortid);
}

inline bool
Shape::matchesParamsAfterId(js::PropertyOp agetter, js::PropertyOp asetter, uint32 aslot,
                            uintN aattrs, uintN aflags, intN ashortid) const
{
    JS_ASSERT(!JSID_IS_VOID(id));
    return rawGetter == agetter &&
           rawSetter == asetter &&
           slot == aslot &&
           attrs == aattrs &&
           ((flags ^ aflags) & PUBLIC_FLAGS) == 0 &&
           shortid == ashortid;
}

inline bool
Shape::get(JSContext* cx, JSObject *receiver, JSObject* obj, JSObject *pobj, js::Value* vp) const
{
    JS_ASSERT(!JSID_IS_VOID(this->id));
    JS_ASSERT(!hasDefaultGetter());

    if (hasGetterValue()) {
        JS_ASSERT(!isMethod());
        js::Value fval = getterValue();
        return js::ExternalGetOrSet(cx, receiver, id, fval, JSACC_READ, 0, 0, vp);
    }

    if (isMethod()) {
        vp->setObject(methodObject());
        return pobj->methodReadBarrier(cx, *this, vp);
    }

    /*
     * |with (it) color;| ends up here, as do XML filter-expressions.
     * Avoid exposing the With object to native getters.
     */
    if (obj->getClass() == &js_WithClass)
        obj = js_UnwrapWithObject(cx, obj);
    return js::CallJSPropertyOp(cx, getterOp(), obj, SHAPE_USERID(this), vp);
}

inline bool
Shape::set(JSContext* cx, JSObject* obj, js::Value* vp) const
{
    JS_ASSERT_IF(hasDefaultSetter(), hasGetterValue());

    if (attrs & JSPROP_SETTER) {
        js::Value fval = setterValue();
        return js::ExternalGetOrSet(cx, obj, id, fval, JSACC_WRITE, 1, vp, vp);
    }

    if (attrs & JSPROP_GETTER)
        return js_ReportGetterOnlyAssignment(cx);

    /* See the comment in js::Shape::get as to why we check for With. */
    if (obj->getClass() == &js_WithClass)
        obj = js_UnwrapWithObject(cx, obj);
    return js::CallJSPropertyOpSetter(cx, setterOp(), obj, SHAPE_USERID(this), vp);
}

inline
EmptyShape::EmptyShape(JSContext *cx, js::Class *aclasp)
  : js::Shape(cx, aclasp)
{
#ifdef DEBUG
    if (cx->runtime->meterEmptyShapes())
        cx->runtime->emptyShapes.put(this);
#endif
}

} /* namespace js */

#endif /* jsscopeinlines_h___ */
