/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * 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-2000 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s):
 * Igor Bukanov
 * Ethan Hugg
 * Terry Lucas
 * Milen Nankov
 *
 * 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.
 */

package org.mozilla.javascript.xmlimpl;

import org.mozilla.javascript.*;
import org.mozilla.javascript.xml.*;

/**
 *  This abstract class describes what all XML objects (XML, XMLList) should have in common.
 *
 * @see XML
 */
abstract class XMLObjectImpl extends XMLObject
{
    private static final Object XMLOBJECT_TAG = new Object();

    protected final XMLLibImpl lib;
    protected boolean prototypeFlag;

    protected XMLObjectImpl(XMLLibImpl lib, XMLObject prototype)
    {
        super(lib.globalScope(), prototype);
        this.lib = lib;
    }

    /**
     * ecmaHas(cx, id) calls this after resolving when id to XMLName
     * and checking it is not Uint32 index.
     */
    abstract boolean hasXMLProperty(XMLName name);

    /**
     * ecmaGet(cx, id) calls this after resolving when id to XMLName
     * and checking it is not Uint32 index.
     */
    abstract Object getXMLProperty(XMLName name);

    /**
     * ecmaPut(cx, id, value) calls this after resolving when id to XMLName
     * and checking it is not Uint32 index.
     */
    abstract void putXMLProperty(XMLName name, Object value);

    /**
     * ecmaDelete(cx, id) calls this after resolving when id to XMLName
     * and checking it is not Uint32 index.
     */
    abstract void deleteXMLProperty(XMLName name);

    /**
     * Test XML equality with target the target.
     */
    abstract boolean equivalentXml(Object target);

    // Methods from section 12.4.4 in the spec
    abstract XML addNamespace(Namespace ns);
    abstract XML appendChild(Object xml);
    abstract XMLList attribute(XMLName xmlName);
    abstract XMLList attributes();
    abstract XMLList child(long index);
    abstract XMLList child(XMLName xmlName);
    abstract int childIndex();
    abstract XMLList children();
    abstract XMLList comments();
    abstract boolean contains(Object xml);
    abstract Object copy();
    abstract XMLList descendants(XMLName xmlName);
    abstract Object[] inScopeNamespaces();
    abstract XML insertChildAfter(Object child, Object xml);
    abstract XML insertChildBefore(Object child, Object xml);
    abstract boolean hasOwnProperty(XMLName xmlName);
    abstract boolean hasComplexContent();
    abstract boolean hasSimpleContent();
    abstract int length();
    abstract String localName();
    abstract QName name();
    abstract Object namespace(String prefix);
    abstract Object[] namespaceDeclarations();
    abstract Object nodeKind();
    abstract void normalize();
    abstract Object parent();
    abstract XML prependChild(Object xml);
    abstract Object processingInstructions(XMLName xmlName);
    abstract boolean propertyIsEnumerable(Object member);
    abstract XML removeNamespace(Namespace ns);
    abstract XML replace(long index, Object xml);
    abstract XML replace(XMLName name, Object xml);
    abstract XML setChildren(Object xml);
    abstract void setLocalName(String name);
    abstract void setName(QName xmlName);
    abstract void setNamespace(Namespace ns);
    abstract XMLList text();
    public abstract String toString();
    abstract String toSource(int indent);
    abstract String toXMLString(int indent);
    abstract Object valueOf();

    /**
     * Extension to access native implementation from scripts
     */
    abstract org.apache.xmlbeans.XmlObject getXmlObject();

    protected abstract Object jsConstructor(Context cx, boolean inNewExpr,
                                            Object[] args);


    final Object getMethod(String id)
    {
        return super.get(id, this);
    }

    //
    //
    // Methods overriding ScriptableObject
    //
    //

    public final Object getDefaultValue(Class hint)
    {
        return toString();
    }

    public void delete(String name)
    {
        throw new IllegalArgumentException("String: [" + name + "]");
    }

    /**
     * XMLObject always compare with any value and equivalentValues
     * never returns {@link Scriptable#NOT_FOUND} for them but rather
     * calls equivalentXml(value) and wrap the result as Boolean.
     */
    protected final Object equivalentValues(Object value)
    {
        boolean result = equivalentXml(value);
        return result ? Boolean.TRUE : Boolean.FALSE;
    }

    //
    //
    // Methods overriding XMLObject
    //
    //

    public final XMLLib lib()
    {
        return lib;
    }

    /**
     * Implementation of ECMAScript [[Has]]
     */
    public final boolean ecmaHas(Context cx, Object id)
    {
        if (cx == null) cx = Context.getCurrentContext();
        XMLName xmlName = lib.toXMLNameOrIndex(cx, id);
        if (xmlName == null) {
            long index = ScriptRuntime.lastUint32Result(cx);
            // XXX Fix this cast
            return has((int)index, this);
        }
        return hasXMLProperty(xmlName);
    }

    /**
     * Implementation of ECMAScript [[Get]]
     */
    public final Object ecmaGet(Context cx, Object id)
    {
        if (cx == null) cx = Context.getCurrentContext();
        XMLName xmlName = lib.toXMLNameOrIndex(cx, id);
        if (xmlName == null) {
            long index = ScriptRuntime.lastUint32Result(cx);
            // XXX Fix this cast
            Object result = get((int)index, this);
            if (result == Scriptable.NOT_FOUND) {
                result = Undefined.instance;
            }
            return result;
        }
        return getXMLProperty(xmlName);
    }

    /**
     * Implementation of ECMAScript [[Put]]
     */
    public final void ecmaPut(Context cx, Object id, Object value)
    {
        if (cx == null) cx = Context.getCurrentContext();
        XMLName xmlName = lib.toXMLNameOrIndex(cx, id);
        if (xmlName == null) {
            long index = ScriptRuntime.lastUint32Result(cx);
            // XXX Fix this cast
            put((int)index, this, value);
            return;
        }
        putXMLProperty(xmlName, value);
    }

    /**
     * Implementation of ECMAScript [[Delete]].
     */
    public final boolean ecmaDelete(Context cx, Object id)
    {
        if (cx == null) cx = Context.getCurrentContext();
        XMLName xmlName = lib.toXMLNameOrIndex(cx, id);
        if (xmlName == null) {
            long index = ScriptRuntime.lastUint32Result(cx);
            // XXX Fix this
            delete((int)index);
            return true;
        }
        deleteXMLProperty(xmlName);
        return true;
    }

    public Ref memberRef(Context cx, Object elem, int memberTypeFlags)
    {
        XMLName xmlName;
        if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) != 0) {
            xmlName = lib.toAttributeName(cx, elem);
        } else {
            if ((memberTypeFlags & Node.DESCENDANTS_FLAG) == 0) {
                // Code generation would use ecma(Get|Has|Delete|Set) for
                // normal name idenrifiers so one ATTRIBUTE_FLAG
                // or DESCENDANTS_FLAG has to be set
                throw Kit.codeBug();
            }
            xmlName = lib.toXMLName(cx, elem);
        }
        if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
            xmlName.setIsDescendants();
        }
        xmlName.initXMLObject(this);
        return xmlName;
    }

    /**
     * Generic reference to implement x::ns, x.@ns::y, x..@ns::y etc.
     */
    public Ref memberRef(Context cx, Object namespace, Object elem,
                         int memberTypeFlags)
    {
        XMLName xmlName = lib.toQualifiedName(cx, namespace, elem);
        if ((memberTypeFlags & Node.ATTRIBUTE_FLAG) != 0) {
            if (!xmlName.isAttributeName()) {
                xmlName.setAttributeName();
            }
        }
        if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
            xmlName.setIsDescendants();
        }
        xmlName.initXMLObject(this);
        return xmlName;
    }

    public NativeWith enterWith(Scriptable scope)
    {
        return new XMLWithScope(lib, scope, this);
    }

    public NativeWith enterDotQuery(Scriptable scope)
    {
        XMLWithScope xws = new XMLWithScope(lib, scope, this);
        xws.initAsDotQuery();
        return xws;
    }

    public final Object addValues(Context cx, boolean thisIsLeft,
                                     Object value)
    {
        if (value instanceof XMLObject) {
            XMLObject v1, v2;
            if (thisIsLeft) {
                v1 = this;
                v2 = (XMLObject)value;
            } else {
                v1 = (XMLObject)value;
                v2 = this;
            }
            return lib.addXMLObjects(cx, v1, v2);
        }
        if (value == Undefined.instance) {
            // both "xml + undefined" and "undefined + xml" gives String(xml)
            return ScriptRuntime.toString(this);
        }

        return super.addValues(cx, thisIsLeft, value);
    }

    //
    //
    // IdScriptableObject machinery
    //
    //

    final void exportAsJSClass(boolean sealed)
    {
        prototypeFlag = true;
        exportAsJSClass(MAX_PROTOTYPE_ID, lib.globalScope(), sealed);
    }

// #string_id_map#
    private final static int
        Id_constructor             = 1,

        Id_addNamespace            = 2,
        Id_appendChild             = 3,
        Id_attribute               = 4,
        Id_attributes              = 5,
        Id_child                   = 6,
        Id_childIndex              = 7,
        Id_children                = 8,
        Id_comments                = 9,
        Id_contains                = 10,
        Id_copy                    = 11,
        Id_descendants             = 12,
        Id_inScopeNamespaces       = 13,
        Id_insertChildAfter        = 14,
        Id_insertChildBefore       = 15,
        Id_hasOwnProperty          = 16,
        Id_hasComplexContent       = 17,
        Id_hasSimpleContent        = 18,
        Id_length                  = 19,
        Id_localName               = 20,
        Id_name                    = 21,
        Id_namespace               = 22,
        Id_namespaceDeclarations   = 23,
        Id_nodeKind                = 24,
        Id_normalize               = 25,
        Id_parent                  = 26,
        Id_prependChild            = 27,
        Id_processingInstructions  = 28,
        Id_propertyIsEnumerable    = 29,
        Id_removeNamespace         = 30,
        Id_replace                 = 31,
        Id_setChildren             = 32,
        Id_setLocalName            = 33,
        Id_setName                 = 34,
        Id_setNamespace            = 35,
        Id_text                    = 36,
        Id_toString                = 37,
        Id_toSource                = 38,
        Id_toXMLString             = 39,
        Id_valueOf                 = 40,

        Id_getXmlObject            = 41,

        MAX_PROTOTYPE_ID           = 41;

    protected int findPrototypeId(String s)
    {
        int id;
// #generated# Last update: 2004-11-10 15:38:11 CET
        L0: { id = 0; String X = null; int c;
            L: switch (s.length()) {
            case 4: c=s.charAt(0);
                if (c=='c') { X="copy";id=Id_copy; }
                else if (c=='n') { X="name";id=Id_name; }
                else if (c=='t') { X="text";id=Id_text; }
                break L;
            case 5: X="child";id=Id_child; break L;
            case 6: c=s.charAt(0);
                if (c=='l') { X="length";id=Id_length; }
                else if (c=='p') { X="parent";id=Id_parent; }
                break L;
            case 7: c=s.charAt(0);
                if (c=='r') { X="replace";id=Id_replace; }
                else if (c=='s') { X="setName";id=Id_setName; }
                else if (c=='v') { X="valueOf";id=Id_valueOf; }
                break L;
            case 8: switch (s.charAt(4)) {
                case 'K': X="nodeKind";id=Id_nodeKind; break L;
                case 'a': X="contains";id=Id_contains; break L;
                case 'd': X="children";id=Id_children; break L;
                case 'e': X="comments";id=Id_comments; break L;
                case 'r': X="toString";id=Id_toString; break L;
                case 'u': X="toSource";id=Id_toSource; break L;
                } break L;
            case 9: switch (s.charAt(2)) {
                case 'c': X="localName";id=Id_localName; break L;
                case 'm': X="namespace";id=Id_namespace; break L;
                case 'r': X="normalize";id=Id_normalize; break L;
                case 't': X="attribute";id=Id_attribute; break L;
                } break L;
            case 10: c=s.charAt(0);
                if (c=='a') { X="attributes";id=Id_attributes; }
                else if (c=='c') { X="childIndex";id=Id_childIndex; }
                break L;
            case 11: switch (s.charAt(0)) {
                case 'a': X="appendChild";id=Id_appendChild; break L;
                case 'c': X="constructor";id=Id_constructor; break L;
                case 'd': X="descendants";id=Id_descendants; break L;
                case 's': X="setChildren";id=Id_setChildren; break L;
                case 't': X="toXMLString";id=Id_toXMLString; break L;
                } break L;
            case 12: switch (s.charAt(0)) {
                case 'a': X="addNamespace";id=Id_addNamespace; break L;
                case 'g': X="getXmlObject";id=Id_getXmlObject; break L;
                case 'p': X="prependChild";id=Id_prependChild; break L;
                case 's': c=s.charAt(3);
                    if (c=='L') { X="setLocalName";id=Id_setLocalName; }
                    else if (c=='N') { X="setNamespace";id=Id_setNamespace; }
                    break L;
                } break L;
            case 14: X="hasOwnProperty";id=Id_hasOwnProperty; break L;
            case 15: X="removeNamespace";id=Id_removeNamespace; break L;
            case 16: c=s.charAt(0);
                if (c=='h') { X="hasSimpleContent";id=Id_hasSimpleContent; }
                else if (c=='i') { X="insertChildAfter";id=Id_insertChildAfter; }
                break L;
            case 17: c=s.charAt(3);
                if (c=='C') { X="hasComplexContent";id=Id_hasComplexContent; }
                else if (c=='c') { X="inScopeNamespaces";id=Id_inScopeNamespaces; }
                else if (c=='e') { X="insertChildBefore";id=Id_insertChildBefore; }
                break L;
            case 20: X="propertyIsEnumerable";id=Id_propertyIsEnumerable; break L;
            case 21: X="namespaceDeclarations";id=Id_namespaceDeclarations; break L;
            case 22: X="processingInstructions";id=Id_processingInstructions; break L;
            }
            if (X!=null && X!=s && !X.equals(s)) id = 0;
        }
// #/generated#
        return id;
    }
// #/string_id_map#

    protected void initPrototypeId(int id)
    {
        String s;
        int arity;
        switch (id) {
          case Id_constructor: {
            IdFunctionObject ctor;
            if (this instanceof XML) {
                ctor = new XMLCtor((XML)this, XMLOBJECT_TAG, id, 1);
            } else {
                ctor = new IdFunctionObject(this, XMLOBJECT_TAG, id, 1);
            }
            initPrototypeConstructor(ctor);
            return;
          }

          case Id_addNamespace:      arity=1; s="addNamespace";      break;
          case Id_appendChild:       arity=1; s="appendChild";       break;
          case Id_attribute:         arity=1; s="attribute";         break;
          case Id_attributes:        arity=0; s="attributes";        break;
          case Id_child:             arity=1; s="child";             break;
          case Id_childIndex:        arity=0; s="childIndex";        break;
          case Id_children:          arity=0; s="children";          break;
          case Id_comments:          arity=0; s="comments";          break;
          case Id_contains:          arity=1; s="contains";          break;
          case Id_copy:              arity=0; s="copy";              break;
          case Id_descendants:       arity=1; s="descendants";       break;
          case Id_hasComplexContent: arity=0; s="hasComplexContent"; break;
          case Id_hasOwnProperty:    arity=1; s="hasOwnProperty";    break;
          case Id_hasSimpleContent:  arity=0; s="hasSimpleContent";  break;
          case Id_inScopeNamespaces: arity=0; s="inScopeNamespaces"; break;
          case Id_insertChildAfter:  arity=2; s="insertChildAfter";  break;
          case Id_insertChildBefore: arity=2; s="insertChildBefore"; break;
          case Id_length:            arity=0; s="length";            break;
          case Id_localName:         arity=0; s="localName";         break;
          case Id_name:              arity=0; s="name";              break;
          case Id_namespace:         arity=1; s="namespace";         break;
          case Id_namespaceDeclarations:
            arity=0; s="namespaceDeclarations"; break;
          case Id_nodeKind:          arity=0; s="nodeKind";          break;
          case Id_normalize:         arity=0; s="normalize";         break;
          case Id_parent:            arity=0; s="parent";            break;
          case Id_prependChild:      arity=1; s="prependChild";      break;
          case Id_processingInstructions:
            arity=1; s="processingInstructions"; break;
          case Id_propertyIsEnumerable:
            arity=1; s="propertyIsEnumerable"; break;
          case Id_removeNamespace:   arity=1; s="removeNamespace";   break;
          case Id_replace:           arity=2; s="replace";           break;
          case Id_setChildren:       arity=1; s="setChildren";       break;
          case Id_setLocalName:      arity=1; s="setLocalName";      break;
          case Id_setName:           arity=1; s="setName";           break;
          case Id_setNamespace:      arity=1; s="setNamespace";      break;
          case Id_text:              arity=0; s="text";              break;
          case Id_toString:          arity=0; s="toString";          break;
          case Id_toSource:          arity=1; s="toSource";          break;
          case Id_toXMLString:       arity=1; s="toXMLString";       break;
          case Id_valueOf:           arity=0; s="valueOf";           break;

          case Id_getXmlObject:      arity=0; s="getXmlObject";      break;
          default: throw new IllegalArgumentException(String.valueOf(id));
        }
        initPrototypeMethod(XMLOBJECT_TAG, id, s, arity);
    }

    /**
     *
     * @param f
     * @param cx
     * @param scope
     * @param thisObj
     * @param args
     * @return
     */
    public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
                             Scriptable thisObj, Object[] args)
    {
        if (!f.hasTag(XMLOBJECT_TAG)) {
            return super.execIdCall(f, cx, scope, thisObj, args);
        }
        int id = f.methodId();
        if (id == Id_constructor) {
            return jsConstructor(cx, thisObj == null, args);
        }

        // All (XML|XMLList).prototype methods require thisObj to be XML
        if (!(thisObj instanceof XMLObjectImpl))
            throw incompatibleCallError(f);
        XMLObjectImpl realThis = (XMLObjectImpl)thisObj;

        switch (id) {
          case Id_addNamespace: {
            Namespace ns = lib.castToNamespace(cx, arg(args, 0));
            return realThis.addNamespace(ns);
          }
          case Id_appendChild:
            return realThis.appendChild(arg(args, 0));
          case Id_attribute: {
            XMLName xmlName = lib.toAttributeName(cx, arg(args, 0));
            return realThis.attribute(xmlName);
          }
          case Id_attributes:
            return realThis.attributes();
          case Id_child: {
            XMLName xmlName = lib.toXMLNameOrIndex(cx, arg(args, 0));
            if (xmlName == null) {
                long index = ScriptRuntime.lastUint32Result(cx);
                return realThis.child(index);
            } else {
                return realThis.child(xmlName);
            }
          }
          case Id_childIndex:
            return ScriptRuntime.wrapInt(realThis.childIndex());
          case Id_children:
            return realThis.children();
          case Id_comments:
            return realThis.comments();
          case Id_contains:
            return ScriptRuntime.wrapBoolean(
                       realThis.contains(arg(args, 0)));
          case Id_copy:
            return realThis.copy();
          case Id_descendants: {
            XMLName xmlName = (args.length == 0)
                              ? XMLName.formStar()
                              : lib.toXMLName(cx, args[0]);
            return realThis.descendants(xmlName);
          }
          case Id_inScopeNamespaces: {
            Object[] array = realThis.inScopeNamespaces();
            return cx.newArray(scope, array);
          }
          case Id_insertChildAfter:
            return realThis.insertChildAfter(arg(args, 0), arg(args, 1));
          case Id_insertChildBefore:
            return realThis.insertChildBefore(arg(args, 0), arg(args, 1));
          case Id_hasOwnProperty: {
            XMLName xmlName = lib.toXMLName(cx, arg(args, 0));
            return ScriptRuntime.wrapBoolean(
                       realThis.hasOwnProperty(xmlName));
          }
          case Id_hasComplexContent:
            return ScriptRuntime.wrapBoolean(realThis.hasComplexContent());
          case Id_hasSimpleContent:
            return ScriptRuntime.wrapBoolean(realThis.hasSimpleContent());
          case Id_length:
            return ScriptRuntime.wrapInt(realThis.length());
          case Id_localName:
            return realThis.localName();
          case Id_name:
            return realThis.name();
          case Id_namespace: {
            String prefix = (args.length > 0)
                            ? ScriptRuntime.toString(args[0]) : null;
            return realThis.namespace(prefix);
          }
          case Id_namespaceDeclarations: {
            Object[] array = realThis.namespaceDeclarations();
            return cx.newArray(scope, array);
          }
          case Id_nodeKind:
            return realThis.nodeKind();
          case Id_normalize:
            realThis.normalize();
            return Undefined.instance;
          case Id_parent:
            return realThis.parent();
          case Id_prependChild:
            return realThis.prependChild(arg(args, 0));
          case Id_processingInstructions: {
            XMLName xmlName = (args.length > 0)
                              ? lib.toXMLName(cx, args[0])
                              : XMLName.formStar();
            return realThis.processingInstructions(xmlName);
          }
          case Id_propertyIsEnumerable: {
            return ScriptRuntime.wrapBoolean(
                       realThis.propertyIsEnumerable(arg(args, 0)));
          }
          case Id_removeNamespace: {
            Namespace ns = lib.castToNamespace(cx, arg(args, 0));
            return realThis.removeNamespace(ns);
          }
          case Id_replace: {
            XMLName xmlName = lib.toXMLNameOrIndex(cx, arg(args, 0));
            Object arg1 = arg(args, 1);
            if (xmlName == null) {
                long index = ScriptRuntime.lastUint32Result(cx);
                return realThis.replace(index, arg1);
            } else {
                return realThis.replace(xmlName, arg1);
            }
          }
          case Id_setChildren:
            return realThis.setChildren(arg(args, 0));
          case Id_setLocalName: {
            String localName;
            Object arg = arg(args, 0);
            if (arg instanceof QName) {
                localName = ((QName)arg).localName();
            } else {
                localName = ScriptRuntime.toString(arg);
            }
            realThis.setLocalName(localName);
            return Undefined.instance;
          }
          case Id_setName: {
            Object arg = (args.length != 0) ? args[0] : Undefined.instance;
            QName qname;
            if (arg instanceof QName) {
                qname = (QName)arg;
                if (qname.uri() == null) {
                    qname = lib.constructQNameFromString(cx, qname.localName());
                } else {
                    // E4X 13.4.4.35 requires to always construct QName
                    qname = lib.constructQName(cx, qname);
                }
            } else {
                qname = lib.constructQName(cx, arg);
            }
            realThis.setName(qname);
            return Undefined.instance;
          }
          case Id_setNamespace: {
            Namespace ns = lib.castToNamespace(cx, arg(args, 0));
            realThis.setNamespace(ns);
            return Undefined.instance;
          }
          case Id_text:
            return realThis.text();
          case Id_toString:
            return realThis.toString();
          case Id_toSource: {
            int indent = ScriptRuntime.toInt32(args, 0);
            return realThis.toSource(indent);
          }
          case Id_toXMLString: {
            int indent = ScriptRuntime.toInt32(args, 0);
            return realThis.toXMLString(indent);
          }
          case Id_valueOf:
            return realThis.valueOf();

          case Id_getXmlObject: {
            org.apache.xmlbeans.XmlObject xmlObject = realThis.getXmlObject();
            return Context.javaToJS(xmlObject, scope);
          }
        }
        throw new IllegalArgumentException(String.valueOf(id));
    }

    private static Object arg(Object[] args, int i)
    {
        return (i < args.length) ? args[i] : Undefined.instance;
    }

}