package org.dbxml.xml.dom;

/*
 * dbXML License, Version 1.0
 *
 *
 * Copyright (c) 1999-2001 The dbXML Group, L.L.C.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by
 *        The dbXML Group (http://www.dbxml.com/)."
 *    Alternately, this acknowledgment may appear in the software
 *    itself, if and wherever such third-party acknowledgments normally
 *    appear.
 *
 * 4. The names "dbXML" and "The dbXML Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    info@dbxml.com.
 *
 * 5. Products derived from this software may not be called "dbXML",
 *    nor may "dbXML" appear in their name, without prior written
 *    permission of The dbXML Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE DBXML GROUP OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: ElementImpl.java,v 1.33 2001/08/06 03:10:10 tom Exp $
 */

import org.dbxml.server.util.*;
import org.dbxml.core.*;
import org.dbxml.core.data.*;
import org.dbxml.xml.*;
import org.dbxml.core.request.*;
import org.dbxml.core.objects.*;

import org.w3c.dom.*;
import java.io.*;
import java.util.Properties;

/**
 * ElementImpl
 */

public final class ElementImpl extends ContainerNodeImpl implements Element {
   private static final String SRC_NS = XMLNS_PREFIX+":src";
   private static final String SRC_COL = "src:"+NodeSource.SOURCE_COL;
   private static final String SRC_KEY = "src:"+NodeSource.SOURCE_KEY;

   private NamedNodeMapImpl attributes = new NamedNodeMapImpl(this);
   private boolean hasAuto = false;
   private short symbolID = -1;

   public ElementImpl() {
   }
   
   public ElementImpl(NodeImpl parentNode, byte[] data, int pos, int len) {
      super(parentNode, data, pos, len);
      DocumentImpl doc = (DocumentImpl)getOwnerDocument();
      try {
         loadAttributes(doc.getSymbols());
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public ElementImpl(NodeImpl parentNode, boolean dirty) {
      super(parentNode, dirty);
   }

   public ElementImpl(NodeImpl parentNode, String nodeName) {
      super(parentNode, true);
      this.nodeName = nodeName;
   }

   protected boolean isNodeTypeValid(short type) {
      return type == Node.ELEMENT_NODE
          || type == Node.COMMENT_NODE
          || type == Node.TEXT_NODE
          || type == Node.CDATA_SECTION_NODE
          || type == Node.ENTITY_REFERENCE_NODE;
   }

   public boolean hasAutoLink() {
      return hasAuto;
   }

   protected void checkLoaded() {
      if ( loaded )
         return;
      else
         loaded = true;

      try {
         if ( data != null ) {
            DocumentImpl doc = (DocumentImpl)getOwnerDocument();
            SymbolTable st = doc.getSymbols();

            ByteArrayInput bis = new ByteArrayInput(data, pos, len);
            XMLCompressedInput in = new XMLCompressedInput(bis, st);

            in.readSignature();   // Skip The Signature
            in.readContentSize(); // Skip The Content Size

            symbolID = in.readShort();
            SymbolTable.SymbolInfo si = st.getSymbolInfo(symbolID);
            nodeName = si.getQName();
            nsURI = si.getNamespaceURI();

            loadChildren(st);

            if ( hasAuto && !doc.isAuthoring() )
               autoLink();
         }
      }
      catch ( Exception e ) {
         org.dbxml.Debug.printStackTrace(e);
      }
   }

   public short getSymbolID() {
      return symbolID;
   }

   public void expandSource() {
      NodeSource src = getSource();
      if ( src != null ) {
         setAttribute(SRC_NS, NodeSource.SOURCE_NS);
         setAttribute(SRC_COL, src.getCollection().getCanonicalName());
         Key k = src.getKey();
         if ( k != null )
            setAttribute(SRC_KEY, k.toString());
      }
   }

   protected void loadAttributes(SymbolTable st) throws IOException {
      ByteArrayInput bis = new ByteArrayInput(data, pos, len);
      XMLCompressedInput in = new XMLCompressedInput(bis, st);

      in.readSignature();
      in.readContentSize();
      short elemSymbol = in.readShort();
      int attrCount = in.readAttributeCount();

      for ( int i = 0 ; i < attrCount; i++ ) {
         short symbol = in.readShort();
         short strLen = in.readShort();
         byte[] b = new byte[strLen];
         in.read(b);
         SymbolTable.SymbolInfo si = st.getSymbolInfo(symbol);
         String name = si.getQName();
         String nsURI = si.getNamespaceURI();
         AttrImpl attr = new AttrImpl(this, name, nsURI, symbol, new String(b));
         attributes.setNamedItem(attr);

         if ( !hasAuto && name.endsWith("href") )
            hasAuto = true;
      }
   }

   private void autoLink() {
      String uri = getAttributeNS(OBJECT_NS, OBJECT_HREF);

      if ( uri != null && uri.length() > 0 ) {
         DocumentImpl doc = (DocumentImpl)getOwnerDocument();
         SymbolTable st = doc.getSymbols();
         Collection col = null;
         NodeSource ds = doc.getSource();
         if ( ds != null )
            col = ds.getCollection();

         if ( uri.indexOf("://") == -1 && !uri.startsWith("/") )
            uri = getSource().getCanonicalName() + '/' + uri;

         if ( col != null ) {
            String type = getAttributeNS(OBJECT_NS, OBJECT_TYPE);
            if ( type == null || type.length() == 0 )
               type = TYPE_REPLACE;

            URIMapper map = null;
            try {
               map = col.getDatabase().getURIMapper(uri);
            }
            catch ( Exception e ) {
               org.dbxml.Debug.printStackTrace(e);
            }

            Node obj = null;
            switch ( map.getObjectType() ) {

               case URIMapper.DOCUMENT:
                  obj = doc.importNode(map.getDocument().getDocumentElement(), true);
                  break;

               case URIMapper.XMLOBJECT:
                  // Resolve The Object/Reflector
                  Reflectable target = map.getXMLObject();
                  String name = map.getMethod();
                  Reflector ref = target.getReflector();

                  // Invoke The Method
                  Object result = null;
                  Properties props = map.getProperties();
                  String[] args = map.getArguments();

                  try {
                     if ( props != null )
                        result = ref.invoke(target, name, new MapArgs(props));
                     else if ( args != null )
                        result = ref.invoke(target, name, args);
                     else
                        result = ref.invoke(target, name, this);
                  }
                  catch ( Exception e ) {
                     org.dbxml.Debug.printStackTrace(e);
                  }

                  // Prepare The Result
                  if ( result != null ) {
                     if ( result instanceof Document )
                        obj = doc.importNode(((Document)result).getDocumentElement(), true);
                     else if ( result instanceof Node )
                        obj = doc.importNode((Node)result, true);
                     else
                        obj = doc.createTextNode(new Variant(result).toString());
                  }
                  break;

            }
            map.reclaim();

            if ( obj != null ) {
               NodeImpl impl = (NodeImpl)obj;
               try {
                  if ( type.equals(TYPE_REPLACE) )
                     parentNode.replaceChild(impl, this);
                  else if ( type.equals(TYPE_CONTENT) ) {
                     childNodes.clear();
                     childNodes.add(impl);
                     impl.setParentNode(this);
                  }
                  else if ( type.equals(TYPE_APPEND) ) {
                     childNodes.add(impl);
                     impl.setParentNode(this);
                  }
                  else if ( type.equals(TYPE_INSERT) ) {
                     childNodes.add(0, impl);
                     impl.setParentNode(this);
                  }
               }
               catch ( Exception e ) {
                  org.dbxml.Debug.println(this, e);
               }
            }
         }
      }
   }

   public short getNodeType() {
      return Node.ELEMENT_NODE;
   }

   /**
    * A <code>NamedNodeMap</code> containing the attributes of this node (if it
    * is an <code>Element</code>) or <code>null</code> otherwise.
    */
   public NamedNodeMap getAttributes() {
      return attributes;
   }

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

   public boolean hasAttribute(String name) {
      return attributes.getNamedItem(name) != null;
   }

   /**
    * Removes an attribute by name. If the removed attribute has a default
    * value it is immediately replaced.
    * @param name The name of the attribute to remove.
    * @exception DOMException
    *   NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    */
   public void removeAttribute(String name) throws DOMException {
      checkReadOnly();
      AttrImpl attr = (AttrImpl)attributes.removeNamedItem(name);
      ((AttrImpl)attr).setParentNode((NodeImpl)this.getOwnerDocument());
      setDirty();
   }

   /**
    * The name of the element. For example, in: &lt;elementExample
    * id="demo"&gt;  ... &lt;/elementExample&gt; , <code>tagName</code> has
    * the value <code>"elementExample"</code>. Note that this is
    * case-preserving in XML, as are all of the operations of the DOM. The
    * HTML DOM returns the <code>tagName</code> of an HTML element in the
    * canonical uppercase form, regardless of the case in the  source HTML
    * document.
    */
   public String getTagName() {
      return getNodeName();
   }

   /**
    * Retrieves an attribute value by name.
    * @param name The name of the attribute to retrieve.
    * @return The <code>Attr</code> value as a string, or the empty  string if
    *   that attribute does not have a specified or default value.
    */
   public String getAttribute(String name) {
      Attr attr = (Attr)attributes.getNamedItem(name);
      return attr != null ? attr.getValue()
                          : "";
   }

   /**
    * Adds a new attribute. If an attribute with that name is already present
    * in the element, it is replaced by the new one.
    * @param newAttr The <code>Attr</code> node to add to the attribute list.
    * @return If the <code>newAttr</code> attribute replaces an existing
    *   attribute with the same name, the  previously existing
    *   <code>Attr</code> node is returned, otherwise <code>null</code> is
    *   returned.
    * @exception DOMException
    *   WRONG_DOCUMENT_ERR: Raised if <code>newAttr</code> was created from a
    *   different document than the one that created the element.
    *   <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    *   <br>INUSE_ATTRIBUTE_ERR: Raised if <code>newAttr</code> is already an
    *   attribute of another <code>Element</code> object. The DOM user must
    *   explicitly clone <code>Attr</code> nodes to re-use them in other
    *   elements.
    */
   public Attr setAttributeNode(Attr newAttr) throws DOMException {
      checkReadOnly();
      Attr oldAttr = (Attr)attributes.getNamedItem(newAttr.getName());
      DocumentImpl doc = (DocumentImpl)getOwnerDocument();
      if ( newAttr.getParentNode().getNodeType() == Node.ELEMENT_NODE )
         throw EX_INUSE_ATTRIBUTE;
      if ( doc != newAttr.getOwnerDocument() )
         throw EX_WRONG_DOCUMENT;
      ((AttrImpl)newAttr).setParentNode(this);
      attributes.setNamedItem(newAttr);
      setDirty();
      return oldAttr;
   }

   /**
    * Adds a new attribute. If an attribute with that name is already present
    * in the element, its value is changed to be that of the value parameter.
    * This value is a simple string, it is not parsed as it is being set. So
    * any markup (such as syntax to be recognized as an entity reference) is
    * treated as literal text, and needs to be appropriately escaped by the
    * implementation when it is written out. In order to assign an attribute
    * value that contains entity references, the user must create an
    * <code>Attr</code> node plus any <code>Text</code> and
    * <code>EntityReference</code> nodes, build the appropriate subtree, and
    * use <code>setAttributeNode</code> to assign it as the value of an
    * attribute.
    * @param name The name of the attribute to create or alter.
    * @param value Value to set in string form.
    * @exception DOMException
    *   INVALID_CHARACTER_ERR: Raised if the specified name contains an
    *   invalid character.
    *   <br>NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    */
   public void setAttribute(String name, String value) throws DOMException {
      Attr attr = getOwnerDocument().createAttribute(name);
      attr.setValue(value);
      setAttributeNode(attr);
   }

   /**
    * Retrieves an <code>Attr</code> node by name.
    * @param name The name of the attribute to retrieve.
    * @return The <code>Attr</code> node with the specified attribute name or
    *   <code>null</code> if there is no such attribute.
    */
   public Attr getAttributeNode(String name) {
      return (Attr)attributes.getNamedItem(name);
   }

   /**
    * Removes the specified attribute.
    * @param oldAttr The <code>Attr</code> node to remove from the attribute
    *   list. If the removed <code>Attr</code> has a default value it is
    *   immediately replaced.
    * @return The <code>Attr</code> node that was removed.
    * @exception DOMException
    *   NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
    *   <br>NOT_FOUND_ERR: Raised if <code>oldAttr</code> is not an attribute
    *   of the element.
    */
   public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
      checkReadOnly();
      DocumentImpl doc = (DocumentImpl)getOwnerDocument();
      if ( oldAttr.getParentNode() != this )
         throw EX_NOT_FOUND;
      attributes.remove(oldAttr.getName());
      ((AttrImpl)oldAttr).setParentNode((NodeImpl)this.getOwnerDocument());
      setDirty();
      return oldAttr;
   }

    /**
     *  Retrieves an attribute value by local name and namespace URI.
     * HTML-only DOM implementations do not need to implement this method.
     * @param namespaceURI  The  namespace URI of the attribute to retrieve.
     * @param localName  The  local name of the attribute to retrieve.
     * @return  The <code>Attr</code> value as a string, or an
     *   <code>null</code> if that attribute does not have a specified or
     *   default value.  This is different from <code>getAttribute</code>
     *   which never return <code>null</code> .
     * @since DOM Level 2
     */
   public String getAttributeNS(String namespaceURI, String localName) {
      Attr attr = (Attr)attributes.getNamedItemNS(namespaceURI, localName);
      return attr != null ? attr.getValue()
                          : null;
   }

    /**
     *  Adds a new attribute. If an attribute with the same local name and
     * namespace URI is already present on the element, its prefix is changed
     * to be the prefix part of the <code>qualifiedName</code> , and its
     * value is changed to be the <code>value</code> parameter. This value is
     * a simple string; it is not parsed as it is being set. So any markup
     * (such as syntax to be recognized as an entity reference) is treated as
     * literal text, and needs to be appropriately escaped by the
     * implementation when it is written out. In order to assign an attribute
     * value that contains entity references, the user must create an
     * <code>Attr</code> node plus any <code>Text</code> and
     * <code>EntityReference</code> nodes, build the appropriate subtree, and
     * use <code>setAttributeNodeNS</code> or <code>setAttributeNode</code> to
     *  assign it as the value of an attribute.
     * <br> HTML-only DOM implementations do not need to implement this method.
     * @param namespaceURI  The  namespace URI of the attribute to create or
     *   alter.
     * @param qualifiedName  The  qualified name of the attribute to create or
     *   alter.
     * @param value  The value to set in string form.
     * @exception DOMException
     *    INVALID_CHARACTER_ERR: Raised if the specified qualified name
     *   contains an illegal character.
     *   <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
     *   <br> NAMESPACE_ERR: Raised if the <code>qualifiedName</code> is
     *   malformed, if the <code>qualifiedName</code> has a prefix and the
     *   <code>namespaceURI</code> is <code>null</code> or an empty string,
     *   if the <code>qualifiedName</code> has a prefix that is "xml" and the
     *   <code>namespaceURI</code> is different from
     *   "http://www.w3.org/XML/1998/namespace", if the
     *   <code>qualifiedName</code> has a prefix that is "xmlns" and the
     *   <code>namespaceURI</code> is different from
     *   "http://www.w3.org/2000/xmlns/", or if the <code>qualifiedName</code>
     *    is "xmlns" and the <code>namespaceURI</code> is different from
     *   "http://www.w3.org/2000/xmlns/".
     * @since DOM Level 2
     */
   public void setAttributeNS(String namespaceURI, String qualifiedName, String value) {
      Attr attr = getOwnerDocument().createAttributeNS(namespaceURI, qualifiedName);
      attr.setValue(value);
      setAttributeNodeNS(attr);
   }

    /**
     *  Removes an attribute by local name and namespace URI. If the removed
     * attribute has a default value it is immediately replaced. The
     * replacing attribute has the same namespace URI and local name, as well
     * as the original prefix.
     * <br> HTML-only DOM implementations do not need to implement this method.
     * @param namespaceURI  The  namespace URI of the attribute to remove.
     * @param localName  The  local name of the attribute to remove.
     * @exception DOMException
     *    NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
     * @since DOM Level 2
     */
   public void removeAttributeNS(String namespaceURI, String localName) {
      checkReadOnly();
      attributes.removeNamedItemNS(namespaceURI, localName);
      setDirty();
   }

    /**
     *  Retrieves an <code>Attr</code> node by local name and namespace URI.
     * HTML-only DOM implementations do not need to implement this method.
     * @param namespaceURI  The  namespace URI of the attribute to retrieve.
     * @param localName  The  local name of the attribute to retrieve.
     * @return  The <code>Attr</code> node with the specified attribute local
     *   name and namespace URI or <code>null</code> if there is no such
     *   attribute.
     * @since DOM Level 2
     */
   public Attr getAttributeNodeNS(String namespaceURI, String localName) {
      return (Attr)attributes.getNamedItemNS(namespaceURI, localName);
   }

    /**
     *  Adds a new attribute. If an attribute with that local name and that
     * namespace URI is already present in the element, it is replaced by the
     * new one.
     * <br> HTML-only DOM implementations do not need to implement this method.
     * @param newAttr  The <code>Attr</code> node to add to the attribute list.
     * @return  If the <code>newAttr</code> attribute replaces an existing
     *   attribute with the same  local name and  namespace URI , the
     *   replaced <code>Attr</code> node is returned, otherwise
     *   <code>null</code> is returned.
     * @exception DOMException
     *    WRONG_DOCUMENT_ERR: Raised if <code>newAttr</code> was created from
     *   a different document than the one that created the element.
     *   <br> NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly.
     *   <br> INUSE_ATTRIBUTE_ERR: Raised if <code>newAttr</code> is already
     *   an attribute of another <code>Element</code> object. The DOM user
     *   must explicitly clone <code>Attr</code> nodes to re-use them in
     *   other elements.
     * @since DOM Level 2
     */
   public Attr setAttributeNodeNS(Attr newAttr) {
      return setAttributeNode(newAttr);
   }

   public boolean hasAttributeNS(String namespaceURI, String localName) {
      return attributes.getNamedItemNS(namespaceURI, localName) != null;
   }
}
