/* Document.java


	Purpose: 
	Description: 
	History:
	2001/10/21 16:36:39, Create, Tom M. Yeh.

Copyright (C) 2001 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.idom;

import java.util.List;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.zkoss.idom.impl.AbstractGroup;
import org.zkoss.idom.impl.DOMImplementation;
import org.zkoss.idom.impl.FacadeNodeList;
import org.zkoss.util.NotableLinkedList;

/**
 * Represents Document which is also W3C/DOM's document,
 * i.e., org.w3c.dom.Document.
 *
 * @author tomyeh
 * @see Element
 */
public class Document extends AbstractGroup implements org.w3c.dom.Document {
	/** The document type. */
	private DocType _docType;
	/** The root element. */
	private Element _root;
	private String _docURI, _ver = "1.0";
	private boolean _stdalone, _stricterrck = true;

	/** Constructor.
	 */
	public Document(Element root, DocType dt) {
		setRootElement(root);
		setDocType(dt);
	}
	/** Constructor.
	 */
	public Document(Element root) {
		setRootElement(root);
	}
	/** Constructor.
	 */
	public Document() {
	}	

	//-- Document extras --//
	/**
	 * Gets the root element.
	 */
	public final Element getRootElement() {
		return _root;
	}
	/**
	 * Sets the root element.
	 */
	public final void setRootElement(Element root) {
		if (root == null) {
			if (_root != null)
				_children.remove(_root);
		} else {
			if (_root != null)
				_children.set(_children.indexOf(_root), root);
			else
				_children.add(root);
		}
	}
	/**
	 * Gets the document type.
	 */
	public final DocType getDocType() {
		return _docType;
	}
	/**
	 * Sets the document type.
	 */
	public final void setDocType(DocType docType) {
		if (docType == null) {
			if (_docType != null)
				_children.remove(_docType);
		} else {
			if (_docType != null)
				_children.set(_children.indexOf(_docType), docType);
			else
				_children.add(docType);
		}
	}

	//-- AbstractGroup --//
	protected final List<Item> newChildren() {
		return new ChildArray(); //note: it doesn't use that of AbstractGroup
	}

	//-- Item --//
	public final String getName() {
		return "#document";
	}

	//-- Node --//
	public final short getNodeType() {
		return DOCUMENT_NODE;
	}

	//-- org.w3c.dom.Document --//
	public final DocumentType getDoctype() {
		return getDocType();
	}
	public final org.w3c.dom.Element getDocumentElement() {
		return getRootElement();
	}
	public final org.w3c.dom.DOMImplementation getImplementation() {
		return DOMImplementation.THE;
	}

	// Unlike Crimson's createXxx, we don't set document here,
	// because, in iDOM, document is decided automatically.

	public final org.w3c.dom.Element createElement(String lname) {
		return new Element(lname);
	}
	public final org.w3c.dom.Element
	createElementNS(String nsURI, String tname) {
		return new Element(nsURI, tname);
	}
	public final Attr createAttribute(String lname) {
		return new Attribute(lname, null);
	}
	public final Attr createAttributeNS(String nsURI, String tname) {
		return new Attribute(nsURI, tname, null);
	}
	public final org.w3c.dom.DocumentFragment createDocumentFragment() {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, getLocator()); //NOT YET
	}
	public final org.w3c.dom.Text createTextNode(String data) {
		return new Text(data);
	}
	public final org.w3c.dom.Comment createComment(String data) {
		return new Comment(data);
	}
	public final CDATASection createCDATASection(String data) {
		return new CData(data);
	}
	public final org.w3c.dom.ProcessingInstruction
	createProcessingInstruction(String target, String data) {
		return new ProcessingInstruction(target, data);
	}
	public final org.w3c.dom.EntityReference
	createEntityReference(String name) {
		return new EntityReference(name);
	}

	/** Gets elements that matches the tag name.
	 *
	 * <p>Unlike other implementations (Xerces or Crimson), the returned list
	 * is a snapshot of the current tree -- not a "live" representation.
	 */
	public final NodeList getElementsByTagName(String tname) {
		return new FacadeNodeList(getElements(tname));
	}
	/** Gets elements that matches the tag name and namespace.
	 *
	 * <p>Unlike other implementations (Xerces or Crimson), the returned list
	 * is a snapshot of the current tree -- not a "live" representation.
	 */
	public final NodeList getElementsByTagNameNS
	(String nsURI, String lname) {
		return new FacadeNodeList(getElements(nsURI, lname, 0));
	}
	public final org.w3c.dom.Element getElementById(String elementId) {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, getLocator()); //NOT YET
	}

	public final Node importNode(Node importedNode, boolean deep) {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, getLocator()); //NOT YET
	}

	public String getInputEncoding() {
		return null; //Level 3 not yet
	}
	public String getXmlEncoding() {
		return null; //Level 3 not yet
	}
	public boolean getXmlStandalone() {
		return _stdalone; //Level 3 not yet
	}
	public void setXmlStandalone(boolean xmlStandalone) throws DOMException {
		_stdalone = xmlStandalone; //Level 3 not yet
	}
	public String getXmlVersion() {
		return _ver;
	}
	public void setXmlVersion(String xmlVersion) throws DOMException {
		_ver = xmlVersion; //Level 3 not yet
	}
	public boolean getStrictErrorChecking() {
		return _stricterrck; //Level 3 not yet
	}
	public void setStrictErrorChecking(boolean strictErrorChecking) {
		_stricterrck = strictErrorChecking; //Level 3 not yet
	}
	public String getDocumentURI() {
		return _docURI; //Level 3 not yet

	}
	public void setDocumentURI(String documentURI) {
		_docURI = documentURI; //Level 3 not yet
	}
	public Node adoptNode(Node source) throws DOMException {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public DOMConfiguration getDomConfig() {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public void normalizeDocument() {
		//Level 3 not yet
	}
	public Node renameNode(Node n, String namespaceURI, String qualifiedName)
	throws DOMException {
		throw new UnsupportedOperationException("DOM Level 3");
	}

	//-- Object --//
	public final String toString() {
		StringBuffer sb = new StringBuffer(128).append("[Document: ");
		if (_docType != null)
			sb.append(_docType.toString());
		if (_root != null)
			sb.append(_root.toString());
		return sb.append(']').toString();
	}

	//-- ChildArray --//
	protected class ChildArray extends NotableLinkedList<Item> {
		protected ChildArray() {
		}
		protected void onAdd(Item newElement, Item followingElement) {
			checkAdd(newElement, followingElement, false);
		}
		protected void onSet(Item newElement, Item replaced) {
			assert(replaced != null);
			checkAdd(newElement, replaced, true);
		}
		private void checkAdd(Item newItem, Item other, boolean replace) {
			//allowed type?
			if (!(newItem instanceof Element) && !(newItem instanceof DocType)
			&& !(newItem instanceof Comment)
			&& !(newItem instanceof ProcessingInstruction)
			&& !(newItem instanceof EntityReference))
				throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
					"Invalid type: "+(newItem!=null ? newItem.getClass(): null)+" "+newItem, getLocator());

			//to be safe, no auto-detach
			if (newItem.getParent() != null)
				throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
					"Item, "+newItem.toString()+", owned by other, "+newItem.getParent(), getLocator());

			//reject add unless no one is set before
			if (Document.this._root != null && (newItem instanceof Element) 
			&& (!replace || !(other instanceof Element)))
				throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
					"Only one root element is allowed, when adding "+newItem, getLocator());
			if (Document.this._docType != null && (newItem instanceof DocType)
			&& (!replace || !(other instanceof DocType)))
				throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
					"Only one document type is allowed, when adding "+newItem, getLocator());

			if (replace)
				onRemove(other);
			newItem.setParent(Document.this);

			if (newItem instanceof Element)
				Document.this._root = (Element)newItem;
			else if (newItem instanceof DocType)
				Document.this._docType = (DocType)newItem;
		}
		protected void onRemove(Item item) {
			item.setParent(null);

			if (item instanceof Element)
				Document.this._root = null;
			else if (item instanceof DocType)
				Document.this._docType = null;
		}
	}
}
