/* -*- 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):
 * 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 java.util.Vector;

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

import org.apache.xmlbeans.XmlCursor;

class XMLList extends XMLObjectImpl implements Function
{
    static final long serialVersionUID = -4543618751670781135L;

    static class AnnotationList
    {
        private Vector v;


        AnnotationList ()
        {
            v = new Vector();
        }


        void add (XML.XScriptAnnotation n)
        {
            v.add(n);
        }


        XML.XScriptAnnotation item(int index)
        {
            return (XML.XScriptAnnotation)(v.get(index));
        }


        void remove (int index)
        {
            v.remove(index);
        }


        int length()
        {
            return v.size();
        }
    };


    // Fields
    private AnnotationList    _annos;

    private XMLObjectImpl targetObject = null;
    private javax.xml.namespace.QName targetProperty = null;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //  Constructors
    //
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     *
     */
    XMLList(XMLLibImpl lib)
    {
        super(lib, lib.xmlListPrototype);
        _annos = new AnnotationList();
    }

    /**
     *
     * @param inputObject
     */
    XMLList(XMLLibImpl lib, Object inputObject)
    {
        super(lib, lib.xmlListPrototype);
        String frag;

        if (inputObject == null || inputObject instanceof Undefined)
        {
            frag = "";
        }
        else if (inputObject instanceof XML)
        {
            XML xml = (XML) inputObject;

            _annos = new AnnotationList();
            _annos.add(xml.getAnnotation());
        }
        else if (inputObject instanceof XMLList)
        {
            XMLList xmll = (XMLList) inputObject;

            _annos = new AnnotationList();

            for (int i = 0; i < xmll._annos.length(); i++)
            {
                _annos.add(xmll._annos.item(i));
            }
        }
        else
        {
            frag = ScriptRuntime.toString(inputObject).trim();

            if (!frag.startsWith("<>"))
            {
                frag = "<>" + frag + "</>";
            }

            frag = "<fragment>" + frag.substring(2);
            if (!frag.endsWith("</>"))
            {
                throw ScriptRuntime.typeError("XML with anonymous tag missing end anonymous tag");
            }

            frag = frag.substring(0, frag.length() - 3) + "</fragment>";

            XML orgXML = XML.createFromJS(lib, frag);

            // Now orphan the children and add them to our XMLList.
            XMLList children = (XMLList)orgXML.children();

            _annos = new AnnotationList();

            for (int i = 0; i < children._annos.length(); i++)
            {
                // Copy here is so that they'll be orphaned (parent() will be undefined)
                _annos.add(((XML) children.item(i).copy()).getAnnotation());
            }
        }
    }

    //
    //
    // TargetObject/Property accessors
    //
    //

    /**
     *
     * @param object
     * @param property
     */
    void setTargets(XMLObjectImpl object, javax.xml.namespace.QName property)
    {
        targetObject = object;
        targetProperty = property;
    }

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //  Private functions
    //
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     *
     * @param index
     * @return
     */
    XML getXmlFromAnnotation(int index)
    {
        XML retVal;

        if (index >= 0 && index < length())
        {
            XML.XScriptAnnotation anno = _annos.item(index);
            retVal = XML.getFromAnnotation(lib, anno);
        }
        else
        {
            retVal = null;
        }

        return retVal;
    }

    /**
     *
     * @param index
     */
    private void internalRemoveFromList (int index)
    {
        _annos.remove(index);
    }

    /**
     *
     * @param index
     * @param xml
     */
    void replace(int index, XML xml)
    {
        if (index < length())
        {
            AnnotationList newAnnoList = new AnnotationList();

            // Copy upto item to replace.
            for (int i = 0; i < index; i++)
            {
                newAnnoList.add(_annos.item(i));
            }

            newAnnoList.add(xml.getAnnotation());

            // Skip over old item we're going to replace we've already add new item on above line.
            for (int i = index + 1; i < length(); i++)
            {
                newAnnoList.add(_annos.item(i));
            }

            _annos = newAnnoList;
        }
    }

    /**
     *
     * @param index
     * @param xml
     */
    private void insert(int index, XML xml)
    {
        if (index < length())
        {
            AnnotationList newAnnoList = new AnnotationList();

            // Copy upto item to insert.
            for (int i = 0; i < index; i++)
            {
                newAnnoList.add(_annos.item(i));
            }

            newAnnoList.add(xml.getAnnotation());

            for (int i = index; i < length(); i++)
            {
                newAnnoList.add(_annos.item(i));
            }

            _annos = newAnnoList;
        }
    }

    //
    //
    //  methods overriding ScriptableObject
    //
    //

    public String getClassName ()
    {
        return "XMLList";
    }

    //
    //
    //  methods overriding IdScriptableObject
    //
    //

    /**
     *
     * @param index
     * @param start
     * @return
     */
    public Object get(int index, Scriptable start)
    {
        //Log("get index: " + index);

        if (index >= 0 && index < length())
        {
            return getXmlFromAnnotation(index);
        }
        else
        {
            return Scriptable.NOT_FOUND;
        }
    }

    /**
     *
     * @param name
     * @param start
     * @return
     */
    boolean hasXMLProperty(XMLName xmlName)
    {
        boolean result = false;

        // Has now should return true if the property would have results > 0 or
        // if it's a method name
        String name = xmlName.localName();
        if ((getPropertyList(xmlName).length() > 0) ||
            (getMethod(name) != NOT_FOUND))
        {
            result = true;
        }

        return result;
    }


    /**
     *
     * @param index
     * @param start
     * @return
     */
    public boolean has(int index, Scriptable start)
    {
        return 0 <= index && index < length();
    }

    /**
     *
     * @param name
     * @param value
     */
    void putXMLProperty(XMLName xmlName, Object value)
    {
        //Log("put property: " + name);

        // Special-case checks for undefined and null
        if (value == null)
        {
            value = "null";
        }
        else if (value instanceof Undefined)
        {
            value = "undefined";
        }

        if (length() > 1)
        {
            throw ScriptRuntime.typeError("Assignment to lists with more that one item is not supported");
        }
        else if (length() == 0)
        {
            // Secret sauce for super-expandos.
            // We set an element here, and then add ourselves to our target.
            if (targetObject != null &&
                targetProperty != null &&
                !targetProperty.getLocalPart().equals("*"))
            {
                // Add an empty element with our targetProperty name and then set it.
                XML xmlValue = XML.createTextElement(lib, targetProperty, "");
                addToList(xmlValue);

                if(xmlName.isAttributeName())
                {
                    setAttribute(xmlName, value);
                }
                else
                {
                    XML xml = (XML)item(0);
                    xml.putXMLProperty(xmlName, value);

                    // Update the list with the new item at location 0.
                    replace(0, (XML)item(0));
                }

                // Now add us to our parent
                XMLName name2 = XMLName.formProperty(targetProperty.getNamespaceURI(), targetProperty.getLocalPart());
                targetObject.putXMLProperty(name2, this);
            }
            else
            {
                throw ScriptRuntime.typeError("Assignment to empty XMLList without targets not supported");
            }
        }
        else if(xmlName.isAttributeName())
        {
            setAttribute(xmlName, value);
        }
        else
        {
            XML xml = (XML)item(0);
            xml.putXMLProperty(xmlName, value);

            // Update the list with the new item at location 0.
            replace(0, (XML)item(0));
        }
    }

    /**
     *
     * @param name
     * @return
     */
    Object getXMLProperty(XMLName name)
    {
        return getPropertyList(name);
    }

    /**
     *
     * @param index
     * @param value
     */
    public void put(int index, Scriptable start, Object value)
    {
        Object parent = Undefined.instance;
        // Convert text into XML if needed.
        XMLObject xmlValue;

        // Special-case checks for undefined and null
        if (value == null)
        {
            value = "null";
        }
        else if (value instanceof Undefined)
        {
            value = "undefined";
        }

        if (value instanceof XMLObject)
        {
            xmlValue = (XMLObject) value;
        }
        else
        {
            if (targetProperty == null)
            {
                xmlValue = XML.createFromJS(lib, value.toString());
            }
            else
            {
                xmlValue = XML.createTextElement(lib, targetProperty, value.toString());
            }
        }

        // Find the parent
        if (index < length())
        {
            parent = item(index).parent();
        }
        else
        {
            // Appending
            parent = parent();
        }

        if (parent instanceof XML)
        {
            // found parent, alter doc
            XML xmlParent = (XML) parent;

            if (index < length())
            {
                // We're replacing the the node.
                XML xmlNode = getXmlFromAnnotation(index);

                if (xmlValue instanceof XML)
                {
                    xmlNode.replaceAll((XML) xmlValue);
                    replace(index, xmlNode);
                }
                else if (xmlValue instanceof XMLList)
                {
                    // Replace the first one, and add the rest on the list.
                    XMLList list = (XMLList) xmlValue;

                    if (list.length() > 0)
                    {
                        int lastIndexAdded = xmlNode.childIndex();
                        xmlNode.replaceAll((XML)list.item(0));
                        replace(index, (XML)list.item(0));

                        for (int i = 1; i < list.length(); i++)
                        {
                            xmlParent.insertChildAfter(xmlParent.getXmlChild(lastIndexAdded), list.item(i));
                            lastIndexAdded++;
                            insert(index + i, (XML)list.item(i));
                        }
                    }
                }
            }
            else
            {
                // Appending
                xmlParent.appendChild(xmlValue);
                addToList(xmlParent.getXmlChild(index));
            }
        }
        else
        {
            // Don't all have same parent, no underlying doc to alter
            if (index < length())
            {
                XML xmlNode = XML.getFromAnnotation(lib, _annos.item(index));

                if (xmlValue instanceof XML)
                {
                    xmlNode.replaceAll((XML) xmlValue);
                    replace(index, xmlNode);
                }
                else if (xmlValue instanceof XMLList)
                {
                    // Replace the first one, and add the rest on the list.
                    XMLList list = (XMLList) xmlValue;

                    if (list.length() > 0)
                    {
                        xmlNode.replaceAll((XML)list.item(0));
                        replace(index, (XML)list.item(0));

                        for (int i = 1; i < list.length(); i++)
                        {
                            insert(index + i, (XML)list.item(i));
                        }
                    }
                }
            }
            else
            {
                addToList(xmlValue);
            }
        }
    }


    /**
     *
     * @param name
     */
    void deleteXMLProperty(XMLName name)
    {
        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);

            if (xml.tokenType() == XmlCursor.TokenType.START)
            {
                xml.deleteXMLProperty(name);
            }
        }
    }

    /**
     *
     * @param index
     */
    public void delete(int index)
    {
        if (index >= 0 && index < length())
        {
            XML xml = getXmlFromAnnotation(index);

            xml.remove();

            internalRemoveFromList(index);
        }
    }


    /**
     *
     * @return
     */
    public Object[] getIds()
    {
        Object enumObjs[];

        if (prototypeFlag)
        {
            enumObjs = new Object[0];
        }
        else
        {
            enumObjs = new Object[length()];

            for (int i = 0; i < enumObjs.length; i++)
            {
                enumObjs[i] = new Integer(i);
            }
        }

        return enumObjs;
    }

    /**
     *
     * @return
     */
    public Object[] getIdsForDebug()
    {
        return getIds();
    }


    // XMLList will remove will delete all items in the list (a set delete) this differs from the XMLList delete operator.
    void remove ()
    {
        int nLen = length();
        for (int i = nLen - 1; i >= 0; i--)
        {
            XML xml = getXmlFromAnnotation(i);
            if (xml != null)
            {
                xml.remove();
                internalRemoveFromList(i);
            }
        }
    }

    /**
     *
     * @param index
     * @return
     */
    XML item (int index)
    {
        return _annos != null
            ? getXmlFromAnnotation(index) : XML.createEmptyXML(lib);
    }


    /**
     *
     * @param name
     * @param value
     */
    private void setAttribute (XMLName xmlName, Object value)
    {
        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);
            xml.setAttribute(xmlName, value);
        }
    }


    /**
     *
     * @param toAdd
     */
    void addToList(Object toAdd)
    {
        if (toAdd instanceof Undefined)
        {
            // Missing argument do nothing...
            return;
        }

        if (toAdd instanceof XMLList)
        {
            XMLList xmlSrc = (XMLList)toAdd;
            for (int i = 0; i < xmlSrc.length(); i++)
            {
                _annos.add(((XML)xmlSrc.item(i)).getAnnotation());
            }
        }
        else if (toAdd instanceof XML)
        {
            _annos.add(((XML)(toAdd)).getAnnotation());
        }
        else if (toAdd instanceof XML.XScriptAnnotation)
        {
            _annos.add((XML.XScriptAnnotation)toAdd);
        }
    }

    //
    //
    // Methods from section 12.4.4 in the spec
    //
    //

    /**
     *
     * @param toAdd
     */
    XML addNamespace(Namespace ns)
    {
        if(length() == 1)
        {
            return getXmlFromAnnotation(0).addNamespace(ns);
        }
        else
        {
            throw ScriptRuntime.typeError("The addNamespace method works only on lists containing one item");
        }
    }

    /**
     *
     * @param xml
     * @return
     */
    XML appendChild(Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).appendChild(xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The appendChild method works only on lists containing one item");
        }
    }

    /**
     *
     * @param attr
     * @return
     */
    XMLList attribute(XMLName xmlName)
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);
            result.addToList(xml.attribute(xmlName));
        }

        return result;
    }

    /**
     *
     * @return
     */
    XMLList attributes()
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);
            result.addToList(xml.attributes());
        }

        return result;
    }

    XMLList child(long index)
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            result.addToList(getXmlFromAnnotation(i).child(index));
        }

        return result;
    }

    XMLList child(XMLName xmlName)
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            result.addToList(getXmlFromAnnotation(i).child(xmlName));
        }

        return result;
    }

    /**
     *
     * @return
     */
    int childIndex()
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).childIndex();
        }
        else
        {
            throw ScriptRuntime.typeError("The childIndex method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    XMLList children()
    {
        Vector v = new Vector();

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);

            if (xml != null)
            {
                Object o = xml.children();
                if (o instanceof XMLList)
                {
                    XMLList childList = (XMLList)o;

                    int cChildren = childList.length();
                    for (int j = 0; j < cChildren; j++)
                    {
                        v.addElement(childList.item(j));
                    }
                }
            }
        }

        XMLList allChildren = new XMLList(lib);
        int sz = v.size();

        for (int i = 0; i < sz; i++)
        {
            allChildren.addToList(v.get(i));
        }

        return allChildren;
    }

    /**
     *
     * @return
     */
    XMLList comments()
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);

            result.addToList(xml.comments());
        }

        return result;
    }

    /**
     *
     * @param xml
     * @return
     */
    boolean contains(Object xml)
    {
        boolean result = false;

        for (int i = 0; i < length(); i++)
        {
            XML member = getXmlFromAnnotation(i);

            if (member.equivalentXml(xml))
            {
                result = true;
                break;
            }
        }

        return result;
    }

    /**
     *
     * @return
     */
    Object copy()
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);
            result.addToList(xml.copy());
        }

        return result;
    }

    /**
     *
     * @return
     */
    XMLList descendants(XMLName xmlName)
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);
            result.addToList(xml.descendants(xmlName));
        }

        return result;
    }

    /**
     *
     * @return
     */
    Object[] inScopeNamespaces()
    {
        if(length() == 1)
        {
            return getXmlFromAnnotation(0).inScopeNamespaces();
        }
        else
        {
            throw ScriptRuntime.typeError("The inScopeNamespaces method works only on lists containing one item");
        }
    }

    /**
     *
     * @param child
     * @param xml
     */
    XML insertChildAfter(Object child, Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).insertChildAfter(child, xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The insertChildAfter method works only on lists containing one item");
        }
    }

    /**
     *
     * @param child
     * @param xml
     */
    XML insertChildBefore(Object child, Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).insertChildAfter(child, xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The insertChildBefore method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    boolean hasOwnProperty(XMLName xmlName)
    {
        boolean hasProperty = false;

        if (prototypeFlag)
        {
            String property = xmlName.localName();
            hasProperty = (0 != findPrototypeId(property));
        }
        else
        {
            hasProperty = (getPropertyList(xmlName).length() > 0);
        }

        return hasProperty;
    }

    /**
     *
     * @return
     */
    boolean hasComplexContent()
    {
        boolean complexContent;
        int length = length();

        if (length == 0)
        {
            complexContent = false;
        }
        else if (length == 1)
        {
            complexContent = getXmlFromAnnotation(0).hasComplexContent();
        }
        else
        {
            complexContent = false;

            for (int i = 0; i < length; i++)
            {
                XML nextElement = getXmlFromAnnotation(i);
                if (nextElement.tokenType() == XmlCursor.TokenType.START)
                {
                    complexContent = true;
                    break;
                }
            }
        }

        return complexContent;
    }

    /**
     *
     * @return
     */
    boolean hasSimpleContent()
    {
        boolean simpleContent;
        int length = length();

        if (length == 0)
        {
            simpleContent = true;
        }
        else if (length == 1)
        {
            simpleContent = getXmlFromAnnotation(0).hasSimpleContent();
        }
        else
        {
            simpleContent = true;

            for (int i = 0; i < length; i++)
            {
                XML nextElement = getXmlFromAnnotation(i);
                if (nextElement.tokenType() == XmlCursor.TokenType.START)
                {
                    simpleContent = false;
                    break;
                }
            }
        }

        return simpleContent;
    }

    /**
     *
     * @return
     */
    int length()
    {
        int result = 0;

        if (_annos != null)
        {
            result = _annos.length();
        }

        return result;
    }

    /**
     *
     * @return
     */
    String localName()
    {
        if (length() == 1)
        {
            return name().localName();
        }
        else
        {
            throw ScriptRuntime.typeError("The localName method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    QName name()
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).name();
        }
        else
        {
            throw ScriptRuntime.typeError("The name method works only on lists containing one item");
        }
    }

    /**
     *
     * @param prefix
     * @return
     */
    Object namespace(String prefix)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).namespace(prefix);
        }
        else
        {
            throw ScriptRuntime.typeError("The namespace method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    Object[] namespaceDeclarations()
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).namespaceDeclarations();
        }
        else
        {
            throw ScriptRuntime.typeError("The namespaceDeclarations method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    Object nodeKind()
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).nodeKind();
        }
        else
        {
            throw ScriptRuntime.typeError("The nodeKind method works only on lists containing one item");
        }
    }

    /**
     *
     */
    void normalize()
    {
        for (int i = 0; i < length(); i++)
        {
            getXmlFromAnnotation(i).normalize();
        }
    }

    /**
     * If list is empty, return undefined, if elements have different parents return undefined,
     * If they all have the same parent, return that parent.
     *
     * @return
     */
    Object parent()
    {
        Object sameParent = Undefined.instance;

        if ((length() == 0) && (targetObject != null) && (targetObject instanceof XML))
        {
            sameParent = targetObject;
        }
        else
        {
            for (int i = 0; i < length(); i++)
            {
                Object currParent = getXmlFromAnnotation(i).parent();

                if (i == 0)
                {
                    // Set the first for the rest to compare to.
                    sameParent = currParent;
                }
                else if (sameParent != currParent)
                {
                    sameParent = Undefined.instance;
                    break;
                }
            }
        }

        // If everything in the list is the sameParent then return that as the parent.
        return sameParent;
    }

    /**
     *
     * @param xml
     * @return
     */
    XML prependChild(Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).prependChild(xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The prependChild method works only on lists containing one item");
        }
    }

    /**
     *
     * @return
     */
    Object processingInstructions(XMLName xmlName)
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            XML xml = getXmlFromAnnotation(i);

            result.addToList(xml.processingInstructions(xmlName));
        }

        return result;
    }

    /**
     *
     * @param name
     * @return
     */
    boolean propertyIsEnumerable(Object name)
    {
        long index;
        if (name instanceof Integer) {
            index = ((Integer)name).intValue();
        } else if (name instanceof Number) {
            double x = ((Number)name).doubleValue();
            index = (long)x;
            if ((double)index != x) {
                return false;
            }
            if (index == 0 && 1.0 / x < 0) {
                // Negative 0
                return false;
            }
        } else {
            String s = ScriptRuntime.toString(name);
            index = ScriptRuntime.testUint32String(s);
        }
        return (0 <= index && index < length());
    }

    /**
     *
     * @param ns
     */
    XML removeNamespace(Namespace ns)
    {
        if(length() == 1)
        {
            return getXmlFromAnnotation(0).removeNamespace(ns);
        }
        else
        {
            throw ScriptRuntime.typeError("The removeNamespace method works only on lists containing one item");
        }
    }

    XML replace(long index, Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).replace(index, xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The replace method works only on lists containing one item");
        }
    }

    /**
     *
     * @param propertyName
     * @param xml
     * @return
     */
    XML replace(XMLName xmlName, Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).replace(xmlName, xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The replace method works only on lists containing one item");
        }
    }

    /**
     *
     * @param xml
     */
    XML setChildren(Object xml)
    {
        if (length() == 1)
        {
            return getXmlFromAnnotation(0).setChildren(xml);
        }
        else
        {
            throw ScriptRuntime.typeError("The setChildren method works only on lists containing one item");
        }
    }

    /**
     *
     * @param name
     */
    void setLocalName(String localName)
    {
        if (length() == 1)
        {
            getXmlFromAnnotation(0).setLocalName(localName);
        }
        else
        {
            throw ScriptRuntime.typeError("The setLocalName method works only on lists containing one item");
        }
    }

    /**
     *
     * @param name
     */
    void setName(QName qname)
    {
        if (length() == 1)
        {
            getXmlFromAnnotation(0).setName(qname);
        }
        else
        {
            throw ScriptRuntime.typeError("The setName method works only on lists containing one item");
        }
    }

    /**
     *
     * @param ns
     */
    void setNamespace(Namespace ns)
    {
        if (length() == 1)
        {
            getXmlFromAnnotation(0).setNamespace(ns);
        }
        else
        {
            throw ScriptRuntime.typeError("The setNamespace method works only on lists containing one item");
        }
    }

    /**
     *
     * * @return
     */
    XMLList text()
    {
        XMLList result = new XMLList(lib);

        for (int i = 0; i < length(); i++)
        {
            result.addToList(getXmlFromAnnotation(i).text());
        }

        return result;
    }

    /**
     *
     * @return
     */
    public String toString()
    {
        if (hasSimpleContent())
        {
            StringBuffer sb = new StringBuffer();

            for(int i = 0; i < length(); i++)
            {
                XML next = getXmlFromAnnotation(i);
                sb.append(next.toString());
            }

            return sb.toString();
        }
        else
        {
            return toXMLString(0);
        }
    }

    String toSource(int indent)
    {
        // XXX indent is ignored
        return "<>"+toXMLString(0)+"</>";
    }

    /**
     *
     * @return
     */
    String toXMLString(int indent)
    {
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < length(); i++)
        {
            if (i > 0)
            {
                sb.append('\n');
            }

            sb.append(getXmlFromAnnotation(i).toXMLString(indent));
        }

        return sb.toString();
    }

    /**
     *
     * @return
     */
    Object valueOf()
    {
        return this;
    }

    //
    // Other public Functions from XMLObject
    //

    /**
     *
     * @param target
     * @return
     */
    boolean equivalentXml(Object target)
    {
        boolean result = false;

        // Zero length list should equate to undefined
        if (target instanceof Undefined && length() == 0)
        {
            result = true;
        }
        else if (length() == 1)
        {
            result = getXmlFromAnnotation(0).equivalentXml(target);
        }
        else if (target instanceof XMLList)
        {
            XMLList otherList = (XMLList) target;

            if (otherList.length() == length())
            {
                result = true;

                for (int i = 0; i < length(); i++)
                {
                    if (!getXmlFromAnnotation(i).equivalentXml(otherList.getXmlFromAnnotation(i)))
                    {
                        result = false;
                        break;
                    }
                }
            }
        }

        return result;
    }

    /**
     *
     * @param name
     * @param start
     * @return
     */
    private XMLList getPropertyList(XMLName name)
    {
        XMLList propertyList = new XMLList(lib);
        javax.xml.namespace.QName qname = null;

        if (!name.isDescendants() && !name.isAttributeName())
        {
            // Only set the targetProperty if this is a regular child get
            // and not a descendant or attribute get
            qname = new javax.xml.namespace.QName(name.uri(), name.localName());
        }

        propertyList.setTargets(this, qname);

        for (int i = 0; i < length(); i++)
        {
            propertyList.addToList(
                getXmlFromAnnotation(i).getPropertyList(name));
        }

        return propertyList;
    }

    private Object applyOrCall(boolean isApply,
                               Context cx, Scriptable scope,
                               Scriptable thisObj, Object[] args)
    {
        String methodName = isApply ? "apply" : "call";
        if(!(thisObj instanceof XMLList) ||
           ((XMLList)thisObj).targetProperty == null)
            throw ScriptRuntime.typeError1("msg.isnt.function",
                                           methodName);

        return ScriptRuntime.applyOrCall(isApply, cx, scope, thisObj, args);
    }

    protected Object jsConstructor(Context cx, boolean inNewExpr,
                                   Object[] args)
    {
        if (args.length == 0) {
            return new XMLList(lib);
        } else {
            Object arg0 = args[0];
            if (!inNewExpr && arg0 instanceof XMLList) {
                // XMLList(XMLList) returns the same object.
                return arg0;
            }
            return new XMLList(lib, arg0);
        }
    }

    org.apache.xmlbeans.XmlObject getXmlObject()
    {
        if (length() == 1) {
            return getXmlFromAnnotation(0).getXmlObject();
        } else {
            throw ScriptRuntime.typeError("getXmlObject method works only on lists containing one item");
        }
    }

    /**
     * See ECMA 357, 11_2_2_1, Semantics, 3_e.
     */
    public Scriptable getExtraMethodSource(Context cx)
    {
        if (length() == 1) {
            return getXmlFromAnnotation(0);
        }
        return null;
    }

    public Object call(Context cx, Scriptable scope, Scriptable thisObj,
                       Object[] args)
    {
        // This XMLList is being called as a Function.
        // Let's find the real Function object.
        if(targetProperty == null)
            throw ScriptRuntime.notFunctionError(this);

        String methodName = targetProperty.getLocalPart();

        boolean isApply = methodName.equals("apply");
        if(isApply || methodName.equals("call"))
            return applyOrCall(isApply, cx, scope, thisObj, args);

        Callable method = ScriptRuntime.getElemFunctionAndThis(
                              this, methodName, cx);
        // Call lastStoredScriptable to clear stored thisObj
        // but ignore the result as the method should use the supplied
        // thisObj, not one from redirected call
        ScriptRuntime.lastStoredScriptable(cx);
        return method.call(cx, scope, thisObj, args);
    }

    public Scriptable construct(Context cx, Scriptable scope, Object[] args)
    {
        throw ScriptRuntime.typeError1("msg.not.ctor", "XMLList");
    }
}


