/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.axis.message;

// $Id$

import org.jboss.axis.AxisFault;
import org.jboss.axis.Constants;
import org.jboss.axis.MessageContext;
import org.jboss.axis.MessagePart;
import org.jboss.axis.encoding.DeserializationContext;
import org.jboss.axis.encoding.DeserializationContextImpl;
import org.jboss.axis.encoding.Deserializer;
import org.jboss.axis.encoding.SerializationContext;
import org.jboss.axis.encoding.SerializationContextImpl;
import org.jboss.axis.enums.Style;
import org.jboss.axis.soap.SOAPConstants;
import org.jboss.axis.utils.Mapping;
import org.jboss.axis.utils.Messages;
import org.jboss.axis.utils.XMLUtils;
import org.jboss.logging.Logger;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.encoding.TypeMapping;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

/**
 * SOAPElementImpl is the base type of nodes of the SOAP message parse tree.
 */
public class SOAPElementAxisImpl extends SOAPElementImpl implements SOAPElement, Cloneable
{
   private static Logger log = Logger.getLogger(SOAPElementAxisImpl.class.getName());

   protected String name;
   protected String prefix;
   protected String namespaceURI;
   protected transient Attributes attributes = NullAttributes.singleton;
   protected String id;
   protected String href;
   protected boolean _isRoot = true;
   protected SOAPEnvelopeAxisImpl message;

   protected transient DeserializationContext context;

   protected transient QName typeQName;

   protected Vector qNameAttrs;

   // Some message representations - as recorded SAX events...
   protected transient SAX2EventRecorder recorder;
   protected int startEventIndex;
   protected int startContentsIndex;
   protected int endEventIndex = -1;

   // ...or as DOM
   protected Element elementRep;

   private SOAPElementAxisImpl parent;

   public ArrayList namespaces;

   /**
    * Our encoding style, if any
    */
   protected String encodingStyle;

   /**
    * Object value, possibly supplied by subclass
    */
   protected Object objectValue;

   // The children of this MessageElement
   private ChildElementList children = new ChildElementList();

   protected MessagePart soapPart;

   // Set this flag to prevent clients from modifying this element
   // eg. ws4ee handlers are not allowed to change the name of every element in the response
   // TDI 15-Jun-2004
   private boolean immutable;

   /**
    * No-arg constructor for building messages?
    */
   public SOAPElementAxisImpl()
   {
      super("unqualified-element");
      name = "unqualified-element";
   }

   public SOAPElementAxisImpl(String localPart)
   {
      super(localPart);
      namespaceURI = "";
      name = localPart;
   }

   public SOAPElementAxisImpl(String namespace, String localPart)
   {
      super(localPart, null, namespace);
      namespaceURI = namespace;
      name = localPart;
   }

   public SOAPElementAxisImpl(String localPart, String prefix, String namespace)
   {
      super(localPart, prefix, namespace);
      this.namespaceURI = namespace;
      this.name = localPart;
      this.prefix = prefix;
      if (prefix != null && prefix.length() > 0)
         addMapping(new Mapping(namespace, prefix));
   }

   public SOAPElementAxisImpl(Name eltName)
   {
      super(eltName);
      this.namespaceURI = eltName.getURI();
      this.name = eltName.getLocalName();
      this.prefix = eltName.getPrefix();
      if (prefix != null && prefix.length() > 0)
         addMapping(new Mapping(namespaceURI, prefix));
   }

   public SOAPElementAxisImpl(String namespace, String localPart, Object value)
   {
      super(localPart, "", namespace);
      this.namespaceURI = namespace;
      this.name = localPart;
      objectValue = value;
   }

   public SOAPElementAxisImpl(QName name, Object value)
   {
      super(name.getLocalPart(), name.getPrefix(), name.getNamespaceURI());
      this.namespaceURI = name.getNamespaceURI();
      this.prefix = name.getPrefix();
      this.name = name.getLocalPart();
      objectValue = value;
   }

   public SOAPElementAxisImpl(Element elem)
   {
      super(elem);
      namespaceURI = elem.getNamespaceURI();
      prefix = elem.getPrefix();
      name = (elem.getLocalName() != null ? elem.getLocalName() : elem.getNodeName());
      elementRep = elem;
   }

   public SOAPElementAxisImpl(String namespace, String localPart, String prefix, Attributes attributes, DeserializationContext context)
           throws AxisFault
   {

      super(localPart, prefix, namespace);
      this.namespaceURI = namespace;
      this.name = localPart;
      this.prefix = prefix;

      if (log.isTraceEnabled())
      {
         log.trace(Messages.getMessage("newElem00", super.toString(), "{" + prefix + "}" + localPart));
         for (int i = 0; attributes != null && i < attributes.getLength(); i++)
            log.trace("  " + attributes.getQName(i) + " = '" + attributes.getValue(i) + "'");
      }

      this.context = context;
      this.startEventIndex = context.getStartOfMappingsPos();

      setNSMappings(context.getCurrentNSMappings());

      this.recorder = context.getRecorder();

      if (attributes != null && attributes.getLength() > 0)
      {
         this.attributes = attributes;

         typeQName = context.getTypeFromAttributes(namespace,
                 localPart,
                 attributes);

         String rootVal = attributes.getValue(Constants.URI_DEFAULT_SOAP_ENC, Constants.ATTR_ROOT);
         if (rootVal != null)
            _isRoot = rootVal.equals("1");

         id = attributes.getValue(Constants.ATTR_ID);
         // Register this ID with the context.....
         if (id != null)
         {
            context.registerElementByID(id, this);
            if (recorder == null)
            {
               recorder = new SAX2EventRecorder();
               context.setRecorder(recorder);
            }
         }

         // Set the encoding style to the attribute value.  If null,
         // we just automatically use our parent's (see getEncodingStyle)
         MessageContext mc = context.getMessageContext();
         SOAPConstants sc = (mc != null) ?
                 mc.getSOAPConstants() :
                 SOAPConstants.SOAP11_CONSTANTS;

         href = attributes.getValue(sc.getAttrHref());

         // If there's an arrayType attribute, we can pretty well guess that we're an Array???
         if (attributes.getValue(Constants.URI_DEFAULT_SOAP_ENC, Constants.ATTR_ARRAY_TYPE) != null)
            typeQName = Constants.SOAP_ARRAY;


         encodingStyle =
                 attributes.getValue(sc.getEncodingURI(),
                         Constants.ATTR_ENCODING_STYLE);

         // if no-encoding style was defined, we don't define as well
         if (Constants.URI_SOAP12_NOENC.equals(encodingStyle))
            encodingStyle = null;

         // If we have an encoding style, and are not a MESSAGE style
         // operation (in other words - we're going to do some data
         // binding), AND we're SOAP 1.2, check the encoding style against
         // the ones we've got type mappings registered for.  If it isn't
         // registered, throw a DataEncodingUnknown fault as per the
         // SOAP 1.2 spec.
         if (encodingStyle != null &&
                 sc.equals(SOAPConstants.SOAP12_CONSTANTS) &&
                 (mc.getOperationStyle() != Style.MESSAGE))
         {
            TypeMapping tm = mc.getTypeMappingRegistry().
                    getTypeMapping(encodingStyle);
            if (tm == null ||
                    (tm.equals(mc.getTypeMappingRegistry().
                    getDefaultTypeMapping())))
            {
               AxisFault badEncodingFault = new AxisFault(Constants.FAULT_SOAP12_DATAENCODINGUNKNOWN,
                       "bad encoding style", null, null);
               throw badEncodingFault;
            }
         }

      }
   }

   /**
    * !!! TODO : Make sure this handles multiple targets
    */
   Deserializer fixupDeserializer;

   public void setFixupDeserializer(Deserializer dser)
   {
      // !!! Merge targets here if already set?
      fixupDeserializer = dser;
   }

   public Deserializer getFixupDeserializer()
   {
      return fixupDeserializer;
   }

   public void setEndIndex(int endIndex)
   {
      endEventIndex = endIndex;
      //context.setRecorder(null);
   }

   /**
    * For ws4ee we should explicitly turn replay of the SAX events of.
    * This is a bad performance hit, but better than a inconsistent soap tree
    * [tdi 05-June-2004]
    */
   private boolean isDirty;

   public boolean isDirty()
   {
      return isDirty;
      //return true;
   }

   public void setDirty(boolean dirty)
   {
      isDirty = dirty;
   }

   public boolean isRoot()
   {
      return _isRoot;
   }

   public String getID()
   {
      return id;
   }

   public String getHref()
   {
      return href;
   }

   public Attributes getAttributesEx()
   {
      return attributes;
   }

   public Node getFirstChild()
   {
      if (children != null && !children.isEmpty())
      {
         return (Node)children.get(0);
      }
      else
      {
         return null;
      }
   }

   public Node getLastChild()
   {
      List children = getChildren();
      if (children != null)
         return (Node)children.get(children.size() - 1);
      else
         return null;
   }

   public Node getNextSibling()
   {
      SOAPElement parent = getParentElement();
      if (parent == null)
      {
         return null;
      }
      Iterator iter = parent.getChildElements();
      Node nextSibling = null;
      while (iter.hasNext())
      {
         if (iter.next().equals(this))
         {
            if (iter.hasNext())
            {
               return (Node)iter.next();
            }
            else
            {
               return null;
            }
         }
      }
      return nextSibling; // should be null.
   }

   public Node getParentNode()
   {
      return parent;
   }

   public Node getPreviousSibling()
   {
      SOAPElement parent = getParentElement();
      Iterator iter = parent.getChildElements();
      Node previousSibling = null;
      while (iter.hasNext())
      {
         if (iter.next().equals(this))
         {
            return previousSibling;
         }
      }
      return previousSibling; // should be null.
   }

   public Node cloneNode(boolean deep)
   {
      try
      {
         SOAPElementAxisImpl clonedSelf = (SOAPElementAxisImpl)this.clonning();

         if (deep == true)
         {
            if (children != null)
            {
               for (int i = 0; i < children.size(); i++)
               {
                  SOAPElementAxisImpl child = (SOAPElementAxisImpl)children.get(i);
                  if (child != null)
                  {  // why child can be null?
                     SOAPElementAxisImpl clonedChild = (SOAPElementAxisImpl)child.cloneNode(deep); // deep == true
                     clonedChild.setParent(clonedSelf);
                     clonedChild.setOwnerDocument(soapPart);
                  }
               }
            }
         }
         return clonedSelf;
      }
      catch (Exception e)
      {
         return null;
      }
   }

   /**
    * protected clone method (not public)
    * <p/>
    * copied status
    * -------------------
    * protected String    name ;             Y
    * protected String    prefix ;           Y
    * protected String    namespaceURI ;     Y
    * protected transient Attributes attributes  Y
    * protected String    id;               Y?
    * protected String    href;             Y?
    * protected boolean   _isRoot = true;   Y?
    * protected SOAPEnvelope message = null; N?
    * protected boolean   _isDirty = false;  Y?
    * protected transient DeserializationContext context;  Y?
    * protected transient QName typeQName = null;          Y?
    * protected Vector qNameAttrs = null;                  Y?
    * protected transient SAX2EventRecorder recorder = null; N?
    * protected int startEventIndex = 0;                   N?
    * protected int startContentsIndex = 0;                N?
    * protected int endEventIndex = -1;                    N?
    * protected Element elementRep = null;                N?
    * protected MessageElement parent = null;             N
    * public ArrayList namespaces = null;                 Y
    * protected String encodingStyle = null;              N?
    * private Object objectValue = null;                 N?
    *
    * @return
    * @throws CloneNotSupportedException
    */
   protected Object clonning() throws CloneNotSupportedException
   {
      try
      {
         SOAPElementAxisImpl clonedME = null;
         clonedME = (SOAPElementAxisImpl)this.clone();

         clonedME.setName(name);
         clonedME.setNamespaceURI(namespaceURI);
         clonedME.setPrefix(prefix);

         // new AttributesImpl will copy all data not set referencing only
         clonedME.setAllAttributes(new AttributesImpl(attributes));
         //       clonedME.addNamespaceDeclaration((namespaces.clone()); // cannot do this. since we cannot access the namepace arraylist

         clonedME.namespaces = new ArrayList();
         if (namespaces != null)
         {
            for (int i = 0; i < namespaces.size(); i++)
            {
               //     jeus.util.Logger.directLog( " Debug :  namspace.size() = " + namespaces.size());
               Mapping namespace = (Mapping)namespaces.get(i);
               clonedME.addNamespaceDeclaration(namespace.getPrefix(), namespace.getNamespaceURI()); // why exception here!!
            }
         }
         // clear reference to old children
         clonedME.detachAllChildren();

         // clear parents relationship to old parent
         clonedME.setParent(null);
         // clonedME.setObjectValue(objectValue); // how to copy this???
         clonedME.setDirty(isDirty());
         if (encodingStyle != null)
         {
            clonedME.setEncodingStyle(new String(encodingStyle));
         }
         clonedME.setRecorder(recorder);
         return clonedME;
      }
      catch (Exception ex)
      {
         return null;
      }
   }

   // called in MESerialaizationContext
   public void setAllAttributes(Attributes attrs)
   {
      attributes = attrs;
   }

   public void detachAllChildren()
   {
      children.clear();
   }

   public NodeList getChildNodes()
   {
      NodeListImpl nodeList = new NodeListImpl();
      for (int i = 0; i < children.size(); i++)
      {
         Node node = (Node)children.get(i);
         nodeList.addNode(node);
      }
      return nodeList;
   }

   public boolean isSupported(String feature, String version)
   {
      return false;  //TODO: Fix this for SAAJ 1.2 Implementation
   }

   public Node appendChild(Node newChild) throws DOMException
   {
      super.appendChild(newChild);
      children.add(newChild);
      return newChild;
   }

   /**
    * Note that this method will log a error and no-op if there is
    * a value (set using setObjectValue) in the MessageElement.
    */
   public void addChild(SOAPElementAxisImpl el) throws SOAPException
   {

      assertImmutable();

      el.parent = this;
      children.add(el);
   }

   /**
    * Throws an JAXRPCException if an attempt is made to modifty this immutable element while
    * RPCProcessing is going on.
    */
   private void assertImmutable()
   {
      SOAPEnvelopeAxisImpl env = getSOAPEnvelope();
      if (env != null && env.isProcessingRPCInvocation() && immutable)
         throw new JAXRPCException("Cannot modify an immutable element");
   }

   public Node removeChild(Node oldChild) throws DOMException
   {

      int position = children.indexOf(oldChild);
      if (position < 0)
         throw new DOMException(DOMException.NOT_FOUND_ERR, "MessageElement Not found");

      // Remove all occurrences in case it has been added multiple times.
      Iterator it = children.iterator();
      while (it.hasNext())
      {
         Node node = (Node)it.next();
         if (node.equals(oldChild))
         {
            if (log.isTraceEnabled())
               log.trace("Remove child node: " + node.getNodeName());

            it.remove();
         }
      }

      setDirty(true);
      return oldChild;
   }

   public Node insertBefore(Node newChild, Node refChild) throws DOMException
   {
      int position = children.indexOf(refChild);
      if (position < 0) position = 0;
      children.add(position, newChild);
      setDirty(true);
      return newChild;
   }

   public Node replaceChild(Node newChild, Node oldChild) throws DOMException
   {
      int position = children.indexOf(oldChild);
      if (position < 0)
         throw new DOMException(DOMException.NOT_FOUND_ERR, "MessageElement Not found");

      children.remove(position);
      children.add(position, newChild);
      setDirty(true);
      return oldChild;
   }

   /**
    * Obtain an Attributes collection consisting of all attributes
    * for this MessageElement, including namespace declarations.
    *
    * @return Attributes collection
    */
   public Attributes getCompleteAttributes()
   {
      if (namespaces == null)
         return attributes;

      AttributesImpl attrs = null;
      if (attributes == NullAttributes.singleton)
         attrs = new AttributesImpl();
      else
         attrs = new AttributesImpl(attributes);

      for (Iterator iterator = namespaces.iterator(); iterator.hasNext();)
      {
         Mapping mapping = (Mapping)iterator.next();
         String prefix = mapping.getPrefix();
         String nsURI = mapping.getNamespaceURI();
         attrs.addAttribute(Constants.NS_URI_XMLNS, prefix,
                 "xmlns:" + prefix, nsURI, "CDATA");
      }
      return attrs;
   }

   public String getName()
   {
      return (name);
   }

   public void setName(String name)
   {
      this.name = name;
   }

   public QName getQName()
   {
      return new QName(namespaceURI, name);
   }

   public void setQName(QName qName)
   {
      this.name = qName.getLocalPart();
      this.namespaceURI = qName.getNamespaceURI();
   }

   public String getPrefix()
   {
      return (prefix);
   }

   public void setPrefix(String prefix)
   {
      this.prefix = prefix;
   }

   public Document getOwnerDocument()
   {
      if (soapPart != null)
         return soapPart;

      if (parent != null)
         return parent.getOwnerDocument();

      return null;
   }

   public NamedNodeMap getAttributes()
   {
      // make first it is editable.
      makeAttributesEditable();
      return convertAttrSAXtoDOM();
   }

   /**
    * [todo] In order to be compatible SAAJ Spec(ver 1.2),
    * The internal representation of Attributes cannot help being changed
    * It is because Attribute is not immutible Type, so if we keep out value and
    * just return it in another form, the application may chnae it, which we cannot
    * detect without some kind back track method (call back notifying the chnage.)
    * I am not sure which approach is better.
    */

   private NamedNodeMap convertAttrSAXtoDOM()
   {
      try
      {
         Document doc = domNode.getOwnerDocument();
         AttributesImpl saxAttrs = (AttributesImpl)attributes;
         NamedNodeMap domAttributes = new NamedNodeMapImpl();
         for (int i = 0; i < saxAttrs.getLength(); i++)
         {
            String uri = saxAttrs.getURI(i);
            String qname = saxAttrs.getQName(i);
            String value = saxAttrs.getValue(i);

            if (uri != null && uri.trim().length() > 0)
            {
               // filterring out the tricky method to differentiate the null namespace
               // -ware case
               if (uri.equals("intentionalNullURI"))
               {
                  uri = null;
               }

               if (uri.equals(Constants.NS_URI_XMLNS) && qname.startsWith("xmlns:") == false)
                  qname = "xmlns:" + qname;

               if (uri.equals(Constants.URI_DEFAULT_SCHEMA_XSI) && qname.startsWith("xsi:") == false)
                  qname = "xsi:" + qname;

               Attr attr = doc.createAttributeNS(uri, qname);
               attr.setValue(value);
               domAttributes.setNamedItemNS(attr);
            }
            else
            {

               Attr attr = doc.createAttribute(qname);
               attr.setValue(value);
               domAttributes.setNamedItem(attr);
            }
         }
         return domAttributes;

      }
      catch (Exception ex)
      {
         log.error("Cannot convert SAX to DOM attributes", ex);
         return null;
      }

   }

   public short getNodeType()
   {
      if (false)
      {
         return DOCUMENT_FRAGMENT_NODE;
      }
      else if (false)
      {
         return Node.ELEMENT_NODE;
      }
      else
      { // most often but we cannot give prioeity now
         return Node.ELEMENT_NODE;
      }
   }

   public void normalize()
   {
      //TODO: Fix this for SAAJ 1.2 Implementation
   }

   public boolean hasAttributes()
   {
      return attributes.getLength() > 0;
   }

   public boolean hasChildNodes()
   {
      return children.size() > 0;
   }

   public String getLocalName()
   {
      return name;
   }

   public String getNamespaceURI()
   {
      return (namespaceURI);
   }

   public String getNodeValue() throws DOMException
   {
      throw new DOMException(DOMException.NO_DATA_ALLOWED_ERR,
              "Cannot use TextNode.get in " + this);
   }

   public void setNamespaceURI(String nsURI)
   {
      namespaceURI = nsURI;
   }

   public QName getType()
   {
      // Try to get the type from our target if we're a reference...
      if (typeQName == null && href != null && context != null)
      {
         SOAPElementAxisImpl referent = context.getElementByID(href);
         if (referent != null)
         {
            typeQName = referent.getType();
         }
      }
      return typeQName;
   }

   public void setType(QName qname)
   {
      typeQName = qname;
   }

   public SAX2EventRecorder getRecorder()
   {
      return recorder;
   }

   public void setRecorder(SAX2EventRecorder rec)
   {
      recorder = rec;
   }

   /**
    * Get the encoding style.  If ours is null, walk up the hierarchy
    * and use our parent's.  Default if we're the root is "".
    *
    * @return the currently in-scope encoding style
    */
   public String getEncodingStyle()
   {
      if (encodingStyle == null)
      {
         if (parent == null)
            return "";
         return parent.getEncodingStyle();
      }
      return encodingStyle;
   }

   public void removeContents()
   {
      // unlink
      if (children != null)
      {
         for (int i = 0; i < children.size(); i++)
         {
            try
            {
               ((SOAPElementAxisImpl)children.get(i)).setParent(null);
            }
            catch (Exception e)
            {
            }
         }
         // empty the collection
         children.clear();
      }
   }

   public Iterator getVisibleNamespacePrefixes()
   {
      Vector prefixes = new Vector();

      // Add all parents namespace definitions
      if (parent != null)
      {
         Iterator parentsPrefixes = parent.getVisibleNamespacePrefixes();
         if (parentsPrefixes != null)
         {
            while (parentsPrefixes.hasNext())
            {
               prefixes.add(parentsPrefixes.next());
            }
         }
      }
      Iterator mine = getNamespacePrefixes();
      if (mine != null)
      {
         while (mine.hasNext())
         {
            prefixes.add(mine.next());
         }
      }
      return prefixes.iterator();
   }

   /**
    * Sets the encoding style for this <CODE>SOAPElement</CODE>
    * object to one specified. The semantics of a null value,
    * as above in getEncodingStyle() are to just use the parent's value,
    * but null here means set to "".
    *
    * @param encodingStyle a <CODE>String</CODE>
    *                      giving the encoding style
    * @throws java.lang.IllegalArgumentException
    *          if
    *          there was a problem in the encoding style being set.
    * @see #getEncodingStyle() getEncodingStyle()
    */
   public void setEncodingStyle(String encodingStyle) throws SOAPException
   {
      if (encodingStyle == null)
      {
         encodingStyle = "";
      }

      this.encodingStyle = encodingStyle;

      // Add the encoding style as an attribute
      // According to the BP-1.0/4.1.7 we should not do that but the s1as interop tests fail otherwise
      // [TDI 11-Sep-2004]
      if (encodingStyle.equals("") == false)
      {
         SOAPConstants soapConstants = SOAPConstants.SOAP11_CONSTANTS;
         addAttribute(getPrefix(soapConstants.getEnvelopeURI()), soapConstants.getEnvelopeURI(), Constants.ATTR_ENCODING_STYLE, encodingStyle);
      }
   }

   private SOAPElementAxisImpl getParent()
   {
      return parent;
   }

   private void setParent(SOAPElementAxisImpl parent) throws SOAPException
   {
      this.parent = parent;
      if (parent != null && !parent.children.contains(this))
      {
         parent.addChild(this);
      }
   }

   /**
    * Remove all child elements
    */
   void removeChildren()
   {
      while (children.isEmpty() == false)
      {
         removeChild((SOAPElementAxisImpl)children.get(0));
      }
   }

   public void setContentsIndex(int index)
   {
      startContentsIndex = index;
   }

   public void setNSMappings(ArrayList namespaces)
   {
      this.namespaces = namespaces;
   }

   public String getPrefix(String namespaceURI)
   {
      if ((namespaceURI == null) || (namespaceURI.equals("")))
         return null;

      if (href != null && getRealElement() != null)
      {
         return getRealElement().getPrefix(namespaceURI);
      }

      for (int i = 0; namespaces != null && i < namespaces.size(); i++)
      {
         Mapping map = (Mapping)namespaces.get(i);
         if (map.getNamespaceURI().equals(namespaceURI))
            return map.getPrefix();
      }

      if (parent != null)
         return parent.getPrefix(namespaceURI);

      return null;
   }

   public String getNamespaceURI(String prefix)
   {
      if (prefix == null)
         prefix = "";

      if (href != null && getRealElement() != null)
      {
         return getRealElement().getNamespaceURI(prefix);
      }

      for (int i = 0; namespaces != null && i < namespaces.size(); i++)
      {
         Mapping map = (Mapping)namespaces.get(i);
         if (map.getPrefix().equals(prefix))
         {
            return map.getNamespaceURI();
         }
      }

      if (parent != null)
         return parent.getNamespaceURI(prefix);

      if (log.isTraceEnabled())
      {
         log.trace(Messages.getMessage("noPrefix00", "" + this, prefix));
      }

      return null;
   }

   /**
    * Returns value of the node as an object of registered type.
    *
    * @return Object of proper type, or null if no mapping could be found.
    */
   public Object getObjectValue()
   {
      Object obj = null;
      try
      {
         obj = getObjectValue(null);
      }
      catch (Exception e)
      {
         log.debug("getValue()", e);
      }
      return obj;
   }

   /**
    * Returns value of the node as an object of registered type.
    *
    * @param cls Class that contains top level deserializer metadata
    * @return Object of proper type, or null if no mapping could be found.
    */
   public Object getObjectValue(Class cls) throws Exception
   {
      if (objectValue == null)
      {
         objectValue = getValueAsType(getType(), cls);
      }
      return objectValue;
   }

   /**
    * Sets value of this node to an Object.
    * A serializer needs to be registered for this object class for proper
    * operation.
    * <p/>
    * Note that this method will log an error and no-op if there are
    * any children in the MessageElement or if the MessageElement was
    * constructed from XML.
    *
    * @param newValue node's value or null.
    */
   public void setObjectValue(Object newValue) throws SOAPException
   {
      if (children != null && !children.isEmpty())
      {
         SOAPException exc = new SOAPException(Messages.getMessage("childPresent"));
         log.error(Messages.getMessage("childPresent"), exc);
         throw exc;
      }
      if (elementRep != null)
      {
         SOAPException exc = new SOAPException(Messages.getMessage("xmlPresent"));
         log.error(Messages.getMessage("xmlPresent"), exc);
         throw exc;
      }
      this.objectValue = newValue;
   }

   public Object getValueAsType(QName type) throws Exception
   {
      return getValueAsType(type, null);
   }

   public Object getValueAsType(QName type, Class cls) throws Exception
   {
      if (context == null)
         throw new Exception(Messages.getMessage("noContext00"));

      Deserializer dser = null;
      if (cls == null)
      {
         dser = context.getDeserializerForType(type);
      }
      else
      {
         // TODO Port from saaj1.2
//            throw new IllegalArgumentException("MessageElement.getValueAsType(): JBoss does not support not null cls now.");
         dser = context.getDeserializerForClass(cls);
      }
      if (dser == null)
         throw new Exception(Messages.getMessage("noDeser00", "" + type));

      boolean oldVal = context.isDoneParsing();
      ((DeserializationContextImpl)context).deserializing(true);
      context.pushElementHandler(new EnvelopeHandler((SOAPHandler)dser));

      publishToHandler((org.xml.sax.ContentHandler)context);

      ((DeserializationContextImpl)context).deserializing(oldVal);

      return dser.getValue();
   }

   protected static class QNameAttr
   {
      QName name;
      QName value;
   }

   public void addAttribute(String namespace, String localName,
                            QName value)
   {
      if (qNameAttrs == null)
         qNameAttrs = new Vector();

      QNameAttr attr = new QNameAttr();
      attr.name = new QName(namespace, localName);
      attr.value = value;

      qNameAttrs.addElement(attr);
      // !!! Add attribute to attributes!
   }

   protected AttributesImpl makeAttributesEditable()
   {
      if (attributes == null || attributes instanceof NullAttributes)
      {
         attributes = new AttributesImpl();
      }
      else if (!(attributes instanceof AttributesImpl))
      {
         attributes = new AttributesImpl(attributes);
      }

      return (AttributesImpl)attributes;
   }

   public void addAttribute(String namespace, String localName, String value)
   {
      AttributesImpl attrs = makeAttributesEditable();
      attrs.addAttribute(namespace, localName, localName, "CDATA", value);
   }

   public void addAttribute(String prefix, String namespace, String localName, String value)
   {
      AttributesImpl attrs = makeAttributesEditable();
      String attrName = localName;
      if (prefix != null && prefix.length() > 0)
      {
         attrName = prefix + ":" + localName;
      }
      attrs.addAttribute(namespace, localName, attrName, "CDATA", value);
   }

   /**
    * Set an attribute, adding the attribute if it isn't already present
    * in this element, and changing the value if it is.  Passing null as the
    * value will cause any pre-existing attribute by this name to go away.
    */
   public void setAttribute(String namespace, String localName, String value)
   {
      AttributesImpl attrs = makeAttributesEditable();
      int idx = attrs.getIndex(namespace, localName);
      if (idx > -1)
      {
         // Got it, so replace it's value.
         if (value != null)
         {
            attrs.setValue(idx, value);
         }
         else
         {
            attrs.removeAttribute(idx);
         }
         return;
      }

      addAttribute(namespace, localName, value);
   }

   public String getAttributeValue(String localName)
   {
      if (attributes == null)
      {
         return null;
      }
      return attributes.getValue(localName);
   }

   public void setEnvelope(SOAPEnvelopeAxisImpl env)
   {
      env.setDirty(true);
      message = env;
   }

   public SOAPEnvelopeAxisImpl getEnvelope()
   {
      return message;
   }

   public SOAPElementAxisImpl getRealElement()
   {
      if (href == null)
         return this;

      Object obj = context.getObjectByRef(href);
      if (obj == null)
         return null;

      if (!(obj instanceof SOAPElementAxisImpl))
         return null;

      return (SOAPElementAxisImpl)obj;
   }

   public Element getAsDOM()
   {
      try
      {
         return getAsDocument().getDocumentElement();
      }
      catch (Exception e)
      {
         log.error("Cannot get element as DOM: " + getElementName(), e);
         return null;
      }
   }

   public Document getAsDocument()
   {

      Document doc = null;
      try
      {
         String elementString = getAsString();
         Reader reader = new StringReader(elementString);
         doc = XMLUtils.newDocument(new InputSource(reader));
      }
      catch (Exception e)
      {
         log.error("Cannot serialize element", e);
         throw new IllegalStateException(e.toString());
      }

      return doc;
   }

   public String getAsString()
   {
      SerializationContext serializeContext = null;
      StringWriter writer = new StringWriter();

      try
      {
         MessageContext msgContext;
         if (context != null)
         {
            msgContext = context.getMessageContext();
         }
         else
         {
            msgContext = MessageContext.getCurrentContext();
         }
         serializeContext = new SerializationContextImpl(writer, msgContext);
         serializeContext.setSendDecl(false);
         output(serializeContext);
         writer.close();
      }
      catch (Exception e)
      {
         log.error("Cannot serialize element", e);
         throw new IllegalStateException(e.toString());
      }

      return writer.getBuffer().toString();
   }

   /**
    * Get a string representation from the internal structure.
    * This should not go on the wire, but is for debugging only.
    */
   public String getAsStringFromInternal()
   {
      try
      {
         StringWriter sw = new StringWriter(1024);
         printFromInternal(new PrintWriter(sw), this);
         return sw.toString();
      }
      catch (Exception e)
      {
         log.error("Cannot parser internal element representation", e);
         return null;
      }
   }

   /**
    * Print the given element from the internal representation
    * This should not go on the wire, but is for debugging only.
    */
   public static void printFromInternal(PrintWriter out, NodeImpl node) throws Exception
   {
      if (node instanceof Text)
      {
         out.print(node.getNodeValue());
         return;
      }

      SOAPElementAxisImpl el = (SOAPElementAxisImpl)node;
      if (el.getChildren().size() == 0)
      {
         printStartElement(out, el);
         printEndElement(out, el);
         return;
      }
      else if (el.getChildren().size() == 1 && el.getFirstChild() instanceof Text)
      {
         printStartElement(out, el);
         out.print(el.getValue());
         printEndElement(out, el);
         return;
      }
      else
      {
         printStartElement(out, el);

         Iterator it = el.getChildren().iterator();
         while (it.hasNext())
         {
            NodeImpl child = (NodeImpl)it.next();
            printFromInternal(out, child);
         }
         printEndElement(out, el);
      }
   }

   private static void printStartElement(PrintWriter out, SOAPElementAxisImpl el)
   {
      if (el.prefix != null && el.prefix.length() > 0)
         out.print("<" + el.prefix + ":" + el.name);
      else
         out.print("<" + el.name);

      for (int i = 0; i < el.attributes.getLength(); i++)
      {
         out.print(" " + el.attributes.getQName(i) + "='" + el.attributes.getValue(i) + "'");
      }

      if (el.namespaces != null)
      {
         for (Iterator i = el.namespaces.iterator(); i.hasNext();)
         {
            Mapping mapping = (Mapping)i.next();
            out.print(" xmlns:" + mapping.getPrefix() + "='" + mapping.getNamespaceURI() + "'");
         }
      }

      if (el.getChildren().size() > 0)
         out.print(">");
      else
         out.print("/>");
   }

   private static void printEndElement(PrintWriter out, SOAPElementAxisImpl el)
   {
      if (el.getChildren().size() > 0)
      {
         if (el.prefix != null && el.prefix.length() > 0)
            out.print("</" + el.prefix + ":" + el.name + ">");
         else
            out.print("</" + el.name + ">");
      }
   }

   public void publishToHandler(ContentHandler handler) throws SAXException
   {
      if (recorder == null)
         throw new SAXException(Messages.getMessage("noRecorder00"));

      recorder.replay(startEventIndex, endEventIndex, handler);
   }

   public void publishContents(ContentHandler handler) throws SAXException
   {
      if (recorder == null)
         throw new SAXException(Messages.getMessage("noRecorder00"));

      recorder.replay(startContentsIndex, endEventIndex - 1, handler);
   }

   /**
    * This is the public output() method, which will always simply use
    * the recorded SAX stream for this element if it is available.  If
    * not, this method calls outputImpl() to allow subclasses and
    * programmatically created messages to serialize themselves.
    *
    * @param context the SerializationContext we will write to.
    */
   public final void output(SerializationContext context) throws Exception
   {

      if ((recorder != null) && (isDirty() == false))
      {
         recorder.replay(startEventIndex, endEventIndex, new SAXOutputter(context));
         return;
      }

      // Turn QName attributes into strings
      if (qNameAttrs != null)
      {
         for (int i = 0; i < qNameAttrs.size(); i++)
         {
            QNameAttr attr = (QNameAttr)qNameAttrs.get(i);
            QName attrName = attr.name;
            setAttribute(attrName.getNamespaceURI(), attrName.getLocalPart(), context.qName2String(attr.value));
         }
      }

      /**
       * Write the encoding style attribute IF it's different from
       * whatever encoding style is in scope....
       */
      if (encodingStyle != null)
      {
         MessageContext mc = context.getMessageContext();
         SOAPConstants soapConstants = (mc != null) ?
                 mc.getSOAPConstants() :
                 SOAPConstants.SOAP11_CONSTANTS;
         if (parent == null)
         {
            // don't emit an encoding style if its "" (literal)
            if (!encodingStyle.equals(""))
            {
               setAttribute(soapConstants.getEnvelopeURI(), Constants.ATTR_ENCODING_STYLE, encodingStyle);
            }
         }
         else if (!encodingStyle.equals(parent.getEncodingStyle()))
         {
            setAttribute(soapConstants.getEnvelopeURI(), Constants.ATTR_ENCODING_STYLE, encodingStyle);
         }
      }

      outputImpl(context);
   }

   /**
    * Subclasses can override
    */
   protected void outputImpl(SerializationContext context) throws Exception
   {
      if (elementRep != null)
      {
         boolean oldPretty = context.getPretty();
         context.setPretty(false);
         context.writeDOMElement(elementRep);
         context.setPretty(oldPretty);
         return;
      }

      if (prefix != null && prefix.length() > 0)
         context.registerPrefixForURI(prefix, namespaceURI);

      if (namespaces != null)
      {
         for (Iterator i = namespaces.iterator(); i.hasNext();)
         {
            Mapping mapping = (Mapping)i.next();
            context.registerPrefixForURI(mapping.getPrefix(), mapping.getNamespaceURI());
         }
      }

      if (objectValue != null)
      {
         context.serialize(new QName(namespaceURI, name),
                 attributes,
                 objectValue, null, false, null);
         return;
      }

      context.startElement(new QName(namespaceURI, name), attributes);
      for (Iterator it = children.iterator(); it.hasNext();)
      {
         Node childNode = (Node)it.next();
         if (childNode instanceof SOAPElementAxisImpl)
            ((SOAPElementAxisImpl)childNode).output(context);
         else if (childNode instanceof TextImpl)
            context.writeString(childNode.getNodeValue());
      }

      context.endElement();
   }

   public void addMapping(Mapping map)
   {
      if (namespaces == null)
         namespaces = new ArrayList();
      namespaces.add(map);
   }

   // JAXM Node methods...

   public void setParentElement(SOAPElement parent) throws SOAPException
   {
      if (parent == null)
         throw new IllegalArgumentException(Messages.getMessage("nullParent00"));
      try
      {
         setParent((SOAPElementAxisImpl)parent);
      }
      catch (Throwable t)
      {
         throw new SOAPException(t);
      }
   }

   public SOAPElement getParentElement()
   {
      return getParent();
   }

   /**
    * Break the relationship between this element and its parent, if any.
    */
   public void detachNode()
   {

      assertImmutable();

      super.detachNode();
      if (parent != null)
      {
         parent.removeChild(this);
         parent = null;
      }
   }

   public boolean isImmutable()
   {
      return immutable;
   }

   public void setImmutable(boolean immutable)
   {
      this.immutable = immutable;
   }

   public void setAllImmutable(boolean immutable)
   {
      this.immutable = immutable;

      Iterator it = children.iterator();
      while (it.hasNext())
      {
         Node node = (Node)it.next();
         if (node instanceof SOAPElementAxisImpl)
            ((SOAPElementAxisImpl)node).setAllImmutable(true);
      }
   }

   // JAXM SOAPElement methods...

   public SOAPElement addChildElement(Name name) throws SOAPException
   {
      SOAPElementAxisImpl child = new SOAPElementAxisImpl(name.getLocalName(),
              name.getPrefix(),
              name.getURI());
      addChild(child);
      return child;
   }

   public SOAPElement addChildElement(String localName) throws SOAPException
   {
      // Inherit parent's namespace
      SOAPElementAxisImpl child = new SOAPElementAxisImpl(getNamespaceURI(),
              localName);
      addChild(child);
      return child;
   }

   public SOAPElement addChildElement(String localName,
                                      String prefix) throws SOAPException
   {
      SOAPElementAxisImpl child = new SOAPElementAxisImpl(getNamespaceURI(prefix),
              localName);
      child.setPrefix(prefix);
      addChild(child);
      return child;
   }

   public SOAPElement addChildElement(String localName,
                                      String prefix,
                                      String uri) throws SOAPException
   {
      SOAPElementAxisImpl child = new SOAPElementAxisImpl(uri, localName);
      child.setPrefix(prefix);
      child.addNamespaceDeclaration(prefix, uri);
      addChild(child);
      return child;
   }

   /**
    * The added child must be an instance of MessageElement rather than
    * an abitrary SOAPElement otherwise a (wrapped) ClassCastException
    * will be thrown.
    */
   public SOAPElement addChildElement(SOAPElement element)
           throws SOAPException
   {
      try
      {
         addChild((SOAPElementAxisImpl)element);
         return element;
      }
      catch (ClassCastException e)
      {
         throw new SOAPException(e);
      }
   }

   /**
    * Text nodes are not supported.
    */
   public SOAPElement addTextNode(String value) throws SOAPException
   {
      org.w3c.dom.Text domText = domNode.getOwnerDocument().createTextNode(value);
      javax.xml.soap.Text soapText = new TextImpl(domText);
      appendChild(soapText);
      return this;
   }

   public SOAPElement addAttribute(Name name, String value)
           throws SOAPException
   {
      try
      {
         addAttribute(name.getPrefix(), name.getURI(), name.getLocalName(), value);
      }
      catch (RuntimeException t)
      {
         throw new SOAPException(t);
      }
      return this;
   }

   public SOAPElement addNamespaceDeclaration(String prefix, String uri)
           throws SOAPException
   {
      try
      {
         Mapping map = new Mapping(uri, prefix);
         addMapping(map);
      }
      catch (RuntimeException t)
      {
         throw new SOAPException(t);
      }
      return this;
   }

   public String getAttributeValue(Name name)
   {
      return attributes.getValue(name.getURI(), name.getLocalName());
   }

   public Iterator getAllAttributes()
   {
      int num = attributes.getLength();
      Vector attrs = new Vector(num);
      for (int i = 0; i < num; i++)
      {
         String q = attributes.getQName(i);
         String prefix = "";
         if (q != null)
         {
            int idx = q.indexOf(":");
            if (idx > 0)
            {
               prefix = q.substring(0, idx);
            }
            else
            {
               prefix = "";
            }
         }

         attrs.add(new NameImpl(attributes.getLocalName(i), prefix, attributes.getURI(i)));
      }
      return attrs.iterator();
   }

   // getNamespaceURI implemented above

   public Iterator getNamespacePrefixes()
   {
      Vector prefixes = new Vector();
      for (int i = 0; namespaces != null && i < namespaces.size(); i++)
      {
         prefixes.add(((Mapping)namespaces.get(i)).getPrefix());
      }
      return prefixes.iterator();
   }

   public Name getElementName()
   {
      return new NameImpl(getName(), getPrefix(), getNamespaceURI());
   }

   public boolean removeAttribute(Name name)
   {
      AttributesImpl attrs = makeAttributesEditable();
      boolean removed = false;

      for (int i = 0; i < attrs.getLength() && !removed; i++)
      {
         if (attrs.getURI(i).equals(name.getURI()) &&
                 attrs.getLocalName(i).equals(name.getLocalName()))
         {
            attrs.removeAttribute(i);
            removed = true;
         }
      }
      return removed;
   }

   public boolean removeNamespaceDeclaration(String prefix)
   {
      makeAttributesEditable();
      boolean removed = false;

      for (int i = 0; namespaces != null && i < namespaces.size() && !removed; i++)
      {
         if (((Mapping)namespaces.get(i)).getPrefix().equals(prefix))
         {
            namespaces.remove(i);
            removed = true;
         }
      }
      return removed;
   }

   public Iterator getChildElements()
   {
      return children.iterator();
   }

   public Iterator getChildElements(Name name)
   {
      ArrayList list = new ArrayList();
      Iterator it = getChildElements();
      while (it.hasNext())
      {
         Node node = (Node)it.next();
         if (name.getURI().equals(node.getNamespaceURI()) && name.getLocalName().equals(node.getLocalName()))
            list.add(node);
      }
      return list.iterator();
   }

   public String getTagName()
   {
      return prefix == null ? name : prefix + ":" + name;
   }

   public void removeAttribute(String name) throws DOMException
   {
      AttributesImpl impl = (AttributesImpl)attributes;
      int index = impl.getIndex(name);
      if (index >= 0)
      {
         AttributesImpl newAttrs = new AttributesImpl();
         // copy except the removed attribute
         for (int i = 0; i < impl.getLength(); i++)
         { // shift after removal
            if (i != index)
            {
               String uri = impl.getURI(i);
               String local = impl.getLocalName(i);
               String qname = impl.getQName(i);
               String type = impl.getType(i);
               String value = impl.getValue(i);
               newAttrs.addAttribute(uri, local, qname, type, value);
            }
         }
         // replace it
         attributes = newAttrs;
      }
   }

   public boolean hasAttribute(String name)
   {
      if (name == null)  // Do I have to send an exception?
         name = "";

      for (int i = 0; i < attributes.getLength(); i++)
      {
         if (name.equals(attributes.getQName(i)))
            return true;
      }
      return false;
   }

   public String getAttribute(String name)
   {
      return attributes.getValue(name);
   }

   public void removeAttributeNS(String namespaceURI, String localName) throws DOMException
   {
      makeAttributesEditable();
      Name name = new NameImpl(localName, null, namespaceURI);
      removeAttribute(name);
   }

   public void setAttribute(String name, String value) throws DOMException
   {
      if (value == null)
         throw new IllegalArgumentException("Cannot set null attribute");

      AttributesImpl attrs = makeAttributesEditable();
      int index = attrs.getIndex(name);
      if (index < 0)
      { // not found
         String uri = "";
         String localname = name;
         String qname = name;
         String type = "CDDATA";
         attrs.addAttribute(uri, localname, qname, type, value);
      }
      else
      {         // found
         attrs.setLocalName(index, value);
      }
   }

   public boolean hasAttributeNS(String namespaceURI, String localName)
   {
      if (namespaceURI == null)
         namespaceURI = "";
      if (localName == null)  // Do I have to send an exception? or just return false
         localName = "";

      for (int i = 0; i < attributes.getLength(); i++)
      {
         if (namespaceURI.equals(attributes.getURI(i))
                 && localName.equals(attributes.getLocalName(i)))
            return true;
      }
      return false;
   }

   public Attr getAttributeNode(String name)
   {
      return null;  //TODO: Fix this for SAAJ 1.2 Implementation
   }

   public Attr removeAttributeNode(Attr oldAttr) throws DOMException
   {
      makeAttributesEditable();
      Name name = new NameImpl(oldAttr.getLocalName(), oldAttr.getPrefix(), oldAttr.getNamespaceURI());
      removeAttribute(name);
      return oldAttr;
   }

   public Attr setAttributeNode(Attr newAttr) throws DOMException
   {
      return newAttr;
   }

   public Attr setAttributeNodeNS(Attr newAttr) throws DOMException
   {
      //attributes.
      AttributesImpl attrs = makeAttributesEditable();
      // how to convert to DOM ATTR
      attrs.addAttribute(newAttr.getNamespaceURI(),
              newAttr.getLocalName(),
              newAttr.getLocalName(),
              "CDATA",
              newAttr.getValue());
      return null;
   }

   public NodeList getElementsByTagName(String name)
   {
      //use this MessageElement class for Nodelist store
      NodeListImpl nodelist = new NodeListImpl();
      if (children != null)
      {
         // add 2nd Generation
         for (int i = 0; i < children.size(); i++)
         {
            Node child = children.get(i);
            if ("*".equals(name) || child.getNodeName().equals(name))
               nodelist.addNode(child);
         }
         // add 3rd Generation
         for (int i = 0; i < children.size(); i++)
         {
            if (children.get(i).getNodeType() == Node.ELEMENT_NODE)
            {
               Element child = (Element)children.get(i);
               NodeList grandsons = child.getElementsByTagName(name);
               for (int j = 0; j < grandsons.getLength(); j++)
               {
                  nodelist.addNode(grandsons.item(j));
               }
            }
         }
      }
      return nodelist;
   }

   public String getAttributeNS(String namespaceURI, String localName)
   {
      for (int i = 0; i < attributes.getLength(); i++)
      {
         if (attributes.getURI(i).equals(namespaceURI) && attributes.getLocalName(i).equals(localName))
         {
            return attributes.getValue(i);
         }
      }
      return null;
   }

   public void setAttributeNS(String namespaceURI, String qualifiedName, String value) throws DOMException
   {
      AttributesImpl attrs = makeAttributesEditable();
      String localName = qualifiedName.substring(qualifiedName.indexOf(":") + 1, qualifiedName.length());

      if (namespaceURI == null)
      {
         namespaceURI = "intentionalNullURI";
      }
      attrs.addAttribute(namespaceURI, localName, qualifiedName, "CDATA", value);
   }

   public Attr getAttributeNodeNS(String namespaceURI, String localName)
   {
      return null;  //TODO: Fix this for SAAJ 1.2 Implementation
   }

   public NodeList getElementsByTagNameNS(String namespaceURI, String localName)
   {
      return getElementsNS(this, namespaceURI, localName);
   }

   /**
    * helper method for recusively getting the element that has namespace URI and localname
    */
   protected NodeList getElementsNS(org.w3c.dom.Element parent,
                                    String namespaceURI, String localName)
   {
      NodeList children = parent.getChildNodes();
      NodeListImpl matches = new NodeListImpl();
      // Add first the imediate child
      for (int i = 0; i < children.getLength(); i++)
      {

         Node c = (Node)children.item(i);
         if (c instanceof Element)
         {
            Element child = (Element)c;
            if (namespaceURI.equals(child.getNamespaceURI()) &&
                    localName.equals(child.getLocalName()))
            {
               matches.addNode(child);
            }
            // search the grand-children.
            matches.addNodeList(child.getElementsByTagNameNS(namespaceURI, localName));
         }
      }
      return matches;
   }

   public void setOwnerDocument(org.jboss.axis.MessagePart sp)
   {
      soapPart = sp;
   }

   /**
    * Mark the SOAPEnvelope as modified, this should invalidate the SAX event cache
    */
   private void setElementAsModified()
   {
      SOAPEnvelopeAxisImpl env = getSOAPEnvelope();
      if (env != null) env.setModified(true);
   }

   /**
    * Get the SOAPEnvelope for this element
    */
   private SOAPEnvelopeAxisImpl getSOAPEnvelope()
   {
      SOAPEnvelopeAxisImpl env = null;
      SOAPElementAxisImpl el = this;
      while (env == null && el != null)
      {
         if (el instanceof SOAPEnvelopeAxisImpl)
         {
            env = (SOAPEnvelopeAxisImpl)el;
         }
         el = el.getParent();
      }
      return env;
   }

   public List getChildren()
   {
      return children.getUnmodifieableList();
   }

   /**
    * Wraps the child list maintained by this class to get better control of who
    * is doing what with respect to children. Eventually this should go all together
    * and the list be maintained by the NodeImpl.
    */
   class ChildElementList
   {
      private ArrayList children = new ArrayList();

      // WRITE operations

      public void clear()
      {
    	  if( log.isTraceEnabled())
    		  log.trace("Clear the child list");

         children.clear();
         setElementAsModified();
      }

      public void add(Node child)
      {
    	  if( log.isTraceEnabled())
    		  log.trace("Adding child: " + getDebugStr(child));

         children.add(child);
         setElementAsModified();
      }

      public void add(int pos, Node child)
      {
    	  if( log.isTraceEnabled())
    		  log.trace("Adding child at position: " + pos + "," + getDebugStr(child));

         children.add(pos, child);
         setElementAsModified();
      }

      public void remove(int pos)
      {
    	  if( log.isTraceEnabled())
    		  log.trace("Remove child at position: " + pos);

         children.remove(pos);
         setElementAsModified();
      }

      // READ operations

      public List getUnmodifieableList()
      {
         return Collections.unmodifiableList(children);
      }

      public boolean contains(Node child)
      {
         return children.contains(child);
      }

      public boolean isEmpty()
      {
         return children.isEmpty();
      }

      public Node get(int pos)
      {
         return (NodeImpl)children.get(pos);
      }

      public int size()
      {
         return children.size();
      }

      public int indexOf(Node child)
      {
         return children.indexOf(child);
      }

      public Iterator iterator()
      {
         return children.iterator();
      }

      private String getDebugStr(Node child)
      {
         String nodeStr = child.getNodeName();
         if (child.getNodeType() == Node.TEXT_NODE)
            nodeStr += " [" + child.getNodeValue() + "]";
         return nodeStr;
      }
   }
}
