/* AbstractItem.java


	Purpose: 
	Description: 
	History:
	2001/10/21 16:26:46, 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.impl;

import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.io.Serializable;
import java.util.regex.Pattern;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.UserDataHandler;

import org.zkoss.idom.DOMException;
import org.zkoss.idom.Document;
import org.zkoss.idom.Group;
import org.zkoss.idom.Item;
import org.zkoss.idom.Namespaceable;
import org.zkoss.xml.Locator;
import org.zkoss.xml.Nodes;

/**
 * A semi-implemented item for leaf vertices.
 * A leaf item is a item without any children.
 * For cores having child cores, use Group.
 *
 * <p>Important methods that the deriving might want to override.
 *
 * <dl>
 *  <dt>Item.getName</dt>
 *  <dd>It must be overridden to provide a local name.</dd>
 *  <dt>Item.setName</dt>
 *  <dd>Override it if it allows to change the name.
 *  Default: throws an exception.</dd>
 *  <dt>Item.getText</dt>
 *  <dd>Override it if it has a text representation.
 *  Default: returns null.</dd>
 *  <dt>Item.setText</dt>
 *  <dd>Override it if it allows to change the text.
 *  Default: throws an exception.</dd>
 *  <dt>Node.getNodeType</dt>
 *  <dd>It must be overridden to provide the type.</dd>
 *  <dt>Item.clone</dt>
 *  <dd>Override it if any other members to handle specially.
 *  Note: by definition, we do deep clone only.</dd>
 *  <dt>Object.toString</dt>
 *  <dd>Override if you want a representation other than
 *  XMLOutputter does.</dd>
 * </dl>
 *
 * @author tomyeh
 * @see org.zkoss.idom.Item
 */
public abstract class AbstractItem
implements Item, Node, Serializable, Cloneable {
    private static final long serialVersionUID = 20060622L;

	/** The parent. */
	private Group _parent;
	/** The locator. */
	private Locator _loc;
	/** The map of user data. */
	private Map<String, Object> _usrdta;

	/** Constructor.
	 */
	protected AbstractItem() {
	}

	//-- utilities --//
	/** Tests whether a namespaceable item matches the criteria.
	 * If mode don't contain FIND_BY_REGEX, ptn is ignored.
	 */
	protected static boolean match
	(Namespaceable vtx, String namespace, String name, Pattern ptn, int mode) {
		if (name != null) {
			String val = (mode & FIND_BY_TAGNAME) != 0 ?
				vtx.getTagName(): vtx.getLocalName();
			if ((mode & FIND_BY_REGEX) != 0) {
				if (!ptn.matcher(val).matches())
					return false;
			} else if ((mode & FIND_IGNORE_CASE) != 0) {
				if (!name.equalsIgnoreCase(val))
					return false;
			} else {
				if (!name.equals(val))
					return false;
			}
		}

		if (namespace != null) {
			String val = (mode & FIND_BY_PREFIX) != 0 ?
				vtx.getNamespace().getPrefix(): vtx.getNamespace().getURI();
			if (!namespace.equals(val))
				return false;
		}
		return true;
	}

	//-- Item --//
	public void setName(String name) {
		throw new DOMException(DOMException.INVALID_ACCESS_ERR, _loc);
	}
	public String getText() {
		return null;
	}
	public void setText(String text) {
		throw new DOMException(DOMException.INVALID_ACCESS_ERR, _loc);
	}

	public Document getDocument() {
		for (Item v = _parent; v != null; v = v.getParent())
			if (v instanceof Document)
				return (Document)v;
		return null;
	}

	public Item detach() {
		if (_parent != null) {
			_parent.getChildren().remove(this);
			assert(_parent == null);
		}
		return this;
	}
	public final Group getParent() {
		return _parent;
	}
	public void setParent(Group parent) {
		_parent = parent;
	}

	public final Locator getLocator() {
		return _loc;
	}
	public final void setLocator(Locator loc) {
		_loc = loc;
	}

	//-- Node --//
	public String getNodeName() {
		return getName();
	}
	public String getNodeValue() {
		return getText();
	}
	public void setNodeValue(String nodeValue) {
		setText(nodeValue);
	}

	public org.w3c.dom.Document getOwnerDocument() {
		return getDocument();
	}
	public final Node cloneNode(boolean deep) {
		if (!deep)
			throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "always deep", _loc); //NOT YET
		return (Node)clone();
	}

	public final Node getParentNode() {
		return (Node)getParent();
	}
	public final Node getPreviousSibling() {
		if (_parent == null)
			return null;

		List list = _parent.getChildren();
		int j = list.indexOf(this);
		if (j < 0)
			throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "internal error", _loc);
		return j == 0 ? null: (Node)list.get(j - 1);
	}
	public final Node getNextSibling() {
		if (_parent == null)
			return null;

		List list = _parent.getChildren();
		int j = list.indexOf(this);
		if (j < 0)
			throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "internal error", _loc);
		return j == list.size()-1 ? null: (Node)list.get(j + 1);
	}
	public NodeList getChildNodes() {
		return Nodes.EMPTY_NODELIST;
	}
	public Node getFirstChild() {
		return null;
	}
	public Node getLastChild() {
		return null;
	}
	public boolean hasChildNodes() {
		return false;
	}
	public Node insertBefore(Node newChild, Node refChild) {
		throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, _loc);
	}
	public Node replaceChild(Node newChild, Node oldChild) {
		throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, _loc);
	}
	public Node removeChild(Node oldChild) {
		throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, _loc);
	}
	public Node appendChild(Node newChild) {
		throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, _loc);
	}

	public final void normalize() {
		throw new DOMException(DOMException.NOT_SUPPORTED_ERR, _loc); //NOT YET
	}
	public final boolean isSupported(String feature, String version) {
		return DOMImplementation.THE.hasFeature(feature, version);
	}

	public String getNamespaceURI() {
		return null;
	}
	public String getPrefix() {
		return null;
	}
	public String getLocalName() {
		return getName();
	}
	public void setPrefix(String prefix) {
		throw new DOMException(DOMException.INVALID_ACCESS_ERR, _loc);
	}

	public NamedNodeMap getAttributes() {
		return EmptyNamedNodeMap.THE;
	}
	public boolean hasAttributes() {
		return false;
	}

	public String getBaseURI() {
		return null; //Level 3 not yet
	}
	public short compareDocumentPosition(Node other) throws DOMException {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public String getTextContent() throws DOMException {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public void setTextContent(String textContent) throws DOMException {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public boolean isSameNode(Node other) {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public String lookupPrefix(String namespaceURI) {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public boolean isDefaultNamespace(String namespaceURI) {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public String lookupNamespaceURI(String prefix) {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public boolean isEqualNode(Node arg) {
		throw new UnsupportedOperationException("DOM Level 3");
	}
	public Object getFeature(String feature, String version) {
		return null;
	}
	public Object setUserData(String key, Object data, UserDataHandler handler) {
		if (handler != null)
			throw new UnsupportedOperationException("DOM Level 3");
		if (_usrdta == null) _usrdta = new LinkedHashMap<String, Object>();
		return _usrdta.put(key, data);
	}
	public Object getUserData(String key) {
		return _usrdta != null ? _usrdta.get(key): null;
	}

	//-- Object --//
	/** Overriding this method is prohibited.
	 */
	public final boolean equals(Object o) {
		return this == o;
	}
	/** Overriding this method is prohibited.
	 */
	public final int hashCode() {
		return super.hashCode();
	}

	//-- Cloneable --//
	/**
	 * Clones this object (a deep cloning not including contents contained
	 * in Textual nodes).
	 * Note: after cloning, the read-only flag always becomes false,
	 * and the parent becomes null (i.e., detached).
	 */
	public Object clone() {
		try {
			AbstractItem v = (AbstractItem)super.clone();
			v._parent = null;
			return v;
		}catch(CloneNotSupportedException ex) {
			throw new InternalError();
		}
	}
}
