package org.orbeon.saxon.xom;

import org.orbeon.saxon.Configuration;
import org.orbeon.saxon.om.StandardNames;
import org.orbeon.saxon.trans.XPathException;
import org.orbeon.saxon.event.Receiver;
import org.orbeon.saxon.om.*;
import org.orbeon.saxon.pattern.AnyNodeTest;
import org.orbeon.saxon.pattern.NameTest;
import org.orbeon.saxon.pattern.NodeKindTest;
import org.orbeon.saxon.pattern.NodeTest;
import org.orbeon.saxon.type.Type;
import org.orbeon.saxon.value.UntypedAtomicValue;
import org.orbeon.saxon.value.Value;
import org.orbeon.saxon.value.StringValue;
import org.orbeon.saxon.value.AtomicValue;
import nu.xom.*;

/**
 * A node in the XML parse tree representing an XML element, character content,
 * or attribute.
 * <P>
 * This is the implementation of the NodeInfo interface used as a wrapper for
 * XOM nodes.
 *
 * @author Michael H. Kay
 * @author Wolfgang Hoschek (ported org.orbeon.saxon.jdom to XOM)
 */

public class NodeWrapper implements NodeInfo, VirtualNode, SiblingCountingNode {

	protected Node node;

	protected short nodeKind;

	private NodeWrapper parent; // null means unknown

	protected DocumentWrapper docWrapper;

	protected int index; // -1 means unknown

	/**
	 * This constructor is protected: nodes should be created using the wrap
	 * factory method on the DocumentWrapper class
	 *
	 * @param node
	 *            The XOM node to be wrapped
	 * @param parent
	 *            The NodeWrapper that wraps the parent of this node
	 * @param index
	 *            Position of this node among its siblings
	 */
	protected NodeWrapper(Node node, NodeWrapper parent, int index) {
		short kind;
		if (node instanceof Element) {
			kind = Type.ELEMENT;
		} else if (node instanceof Text) {
			kind = Type.TEXT;
		} else if (node instanceof Attribute) {
			kind = Type.ATTRIBUTE;
		} else if (node instanceof Comment) {
			kind = Type.COMMENT;
		} else if (node instanceof ProcessingInstruction) {
			kind = Type.PROCESSING_INSTRUCTION;
		} else if (node instanceof Document) {
			kind = Type.DOCUMENT;
		} else {
			throwIllegalNode(node); // moved out of fast path to enable better inlining
			return; // keep compiler happy
		}
        nodeKind = kind;
		this.node = node;
		this.parent = parent;
		this.index = index;
	}

	/**
	 * Factory method to wrap a XOM node with a wrapper that implements the
	 * Saxon NodeInfo interface.
	 *
	 * @param node
	 *            The XOM node
	 * @param docWrapper
	 *            The wrapper for the Document containing this node
	 * @return The new wrapper for the supplied node
	 */
	protected final NodeWrapper makeWrapper(Node node, DocumentWrapper docWrapper) {
		return makeWrapper(node, docWrapper, null, -1);
	}

	/**
	 * Factory method to wrap a XOM node with a wrapper that implements the
	 * Saxon NodeInfo interface.
	 *
	 * @param node
	 *            The XOM node
	 * @param docWrapper
	 *            The wrapper for the Document containing this node
	 * @param parent
	 *            The wrapper for the parent of the XOM node
	 * @param index
	 *            The position of this node relative to its siblings
	 * @return The new wrapper for the supplied node
	 */

	protected final NodeWrapper makeWrapper(Node node, DocumentWrapper docWrapper,
			NodeWrapper parent, int index) {

		if (node == docWrapper.node) return docWrapper;
		NodeWrapper wrapper = new NodeWrapper(node, parent, index);
        wrapper.docWrapper = docWrapper;
		return wrapper;
	}

    private static void throwIllegalNode(Node node) {
		String str = node == null ?
				"NULL" :
				node.getClass() + " instance " + node.toString();
		throw new IllegalArgumentException("Bad node type in XOM! " + str);
	}

    /**
     * Get the configuration
     */

    public Configuration getConfiguration() {
        return docWrapper.getConfiguration();
    }

	/**
	 * Get the underlying XOM node, to implement the VirtualNode interface
	 */

	public Object getUnderlyingNode() {
		return node;
	}

	/**
	 * Get the name pool for this node
	 *
	 * @return the NamePool
	 */

	public NamePool getNamePool() {
		return docWrapper.getNamePool();
	}

	/**
	 * Return the type of node.
	 *
	 * @return one of the values Node.ELEMENT, Node.TEXT, Node.ATTRIBUTE, etc.
	 */

	public int getNodeKind() {
		return nodeKind;
	}

	/**
	 * Get the typed value of the item
	 */

	public SequenceIterator getTypedValue() {
		return SingletonIterator.makeIterator((AtomicValue)atomize());
	}

    /**
	 * Get the typed value. The result of this method will always be consistent
	 * with the method {@link org.orbeon.saxon.om.Item#getTypedValue()}. However,
	 * this method is often more convenient and may be more efficient,
	 * especially in the common case where the value is expected to be a
	 * singleton.
	 *
	 * @return the typed value. If requireSingleton is set to true, the result
	 *         will always be an AtomicValue. In other cases it may be a Value
	 *         representing a sequence whose items are atomic values.
	 * @since 8.5
	 */

	public Value atomize() {
        switch (getNodeKind()) {
            case Type.COMMENT:
            case Type.PROCESSING_INSTRUCTION:
                return new StringValue(getStringValueCS());
            default:
                return new UntypedAtomicValue(getStringValueCS());
        }
	}

    /**
	 * Get the type annotation of this node, if any. Returns -1 for kinds of
	 * nodes that have no annotation, and for elements annotated as untyped, and
	 * attributes annotated as untypedAtomic.
	 *
	 * @return the type annotation of the node.
	 * @see org.orbeon.saxon.type.Type
	 */

	public int getTypeAnnotation() {
        if (getNodeKind() == Type.ATTRIBUTE) {
            return StandardNames.XS_UNTYPED_ATOMIC;
        }
        return StandardNames.XS_UNTYPED;
	}

	/**
	 * Determine whether this is the same node as another node. <br />
	 * Note: a.isSameNode(b) if and only if generateId(a)==generateId(b)
	 *
	 * @return true if this Node object and the supplied Node object represent
	 *         the same node in the tree.
	 */

	public boolean isSameNodeInfo(NodeInfo other) {
        // In XOM equality means identity
        return other instanceof NodeWrapper && node == ((NodeWrapper)other).node;
    }

    /**
     * The equals() method compares nodes for identity. It is defined to give the same result
     * as isSameNodeInfo().
     *
     * @param other the node to be compared with this node
     * @return true if this NodeInfo object and the supplied NodeInfo object represent
     *         the same node in the tree.
     * @since 8.7 Previously, the effect of the equals() method was not defined. Callers
     *        should therefore be aware that third party implementations of the NodeInfo interface may
     *        not implement the correct semantics. It is safer to use isSameNodeInfo() for this reason.
     *        The equals() method has been defined because it is useful in contexts such as a Java Set or HashMap.
     */

    public boolean equals(Object other) {
        return other instanceof NodeInfo && isSameNodeInfo((NodeInfo)other);
    }

     /**
      * The hashCode() method obeys the contract for hashCode(): that is, if two objects are equal
      * (represent the same node) then they must have the same hashCode()
      * @since 8.7 Previously, the effect of the equals() and hashCode() methods was not defined. Callers
      * should therefore be aware that third party implementations of the NodeInfo interface may
      * not implement the correct semantics.
      */

     public int hashCode() {
         return node.hashCode();
     }

	/**
	 * Get the System ID for the node.
	 *
	 * @return the System Identifier of the entity in the source document
	 *         containing the node, or null if not known. Note this is not the
	 *         same as the base URI: the base URI can be modified by xml:base,
	 *         but the system ID cannot.
	 */

	public String getSystemId() {
		return docWrapper.baseURI;
	}

	public void setSystemId(String uri) {
		docWrapper.baseURI = uri;
	}

	/**
	 * Get the Base URI for the node, that is, the URI used for resolving a
	 * relative URI contained in the node.
	 */

	public String getBaseURI() {
		return node.getBaseURI();
	}

	/**
	 * Get line number
	 *
	 * @return the line number of the node in its original source document; or
	 *         -1 if not available
	 */

	public int getLineNumber() {
		return -1;
	}

   /**
     * Get column number
     * @return the column number of the node in its original source document; or -1 if not available
     */

    public int getColumnNumber() {
        return -1;
    }        

    /**
	 * Determine the relative position of this node and another node, in
	 * document order. The other node will always be in the same document.
	 *
	 * @param other
	 *            The other node, whose position is to be compared with this
	 *            node
	 * @return -1 if this node precedes the other node, +1 if it follows the
	 *         other node, or 0 if they are the same node. (In this case,
	 *         isSameNode() will always return true, and the two nodes will
	 *         produce the same result for generateId())
	 */

	public int compareOrder(NodeInfo other) {
		if (other instanceof NodeWrapper) {
			return compareOrderFast(node,((NodeWrapper) other).node);
		} else {
			// it must be a namespace node
			return -other.compareOrder(this);
		}
	}

    private static int compareOrderFast(Node first, Node second) {
		/*
		 * Unfortunately we do not have a sequence number for each node at hand;
		 * this would allow to turn the comparison into a simple sequence number
		 * subtraction. Walking the entire tree and batch-generating sequence
		 * numbers on the fly is no good option either. However, this rewritten
		 * implementation turns out to be more than fast enough.
		 */

		// assert first != null && second != null
    		// assert first and second MUST NOT be namespace nodes
		if (first == second) return 0;

		ParentNode firstParent = first.getParent();
		ParentNode secondParent = second.getParent();
		if (firstParent == null) {
			if (secondParent != null) return -1; // first node is the root
			// both nodes are parentless, use arbitrary but fixed order:
			return first.hashCode() - second.hashCode();
		}

		if (secondParent == null) return +1; // second node is the root

		// do they have the same parent (common case)?
		if (firstParent == secondParent) {
			int i1 = firstParent.indexOf(first);
			int i2 = firstParent.indexOf(second);

			// note that attributes and namespaces are not children
			// of their own parent (i = -1).
			// attribute (if any) comes before child
			if (i1 != -1) return (i2 != -1) ? i1 - i2 : +1;
			if (i2 != -1) return -1;

			// assert: i1 == -1 && i2 == -1
			// i.e. both nodes are attributes
			Element elem = (Element) firstParent;
			for (int i = elem.getAttributeCount(); --i >= 0;) {
				Attribute attr = elem.getAttribute(i);
				if (attr == second) return -1;
				if (attr == first) return +1;
			}
			throw new IllegalStateException("should be unreachable");
		}

		// find the depths of both nodes in the tree
		int depth1 = 0;
		int depth2 = 0;
		Node p1 = first;
		Node p2 = second;
		while (p1 != null) {
			depth1++;
			p1 = p1.getParent();
			if (p1 == second) return +1;
		}
		while (p2 != null) {
			depth2++;
			p2 = p2.getParent();
			if (p2 == first) return -1;
		}

		// move up one branch of the tree so we have two nodes on the same level
		p1 = first;
		while (depth1 > depth2) {
			p1 = p1.getParent();
			depth1--;
		}
		p2 = second;
		while (depth2 > depth1) {
			p2 = p2.getParent();
			depth2--;
		}

		// now move up both branches in sync until we find a common parent
		while (true) {
			firstParent = p1.getParent();
			secondParent = p2.getParent();
			if (firstParent == null || secondParent == null) {
				// both nodes are documentless, use arbitrary but fixed order
				// based on their root elements
				return p1.hashCode() - p2.hashCode();
				// throw new NullPointerException("XOM tree compare - internal error");
			}
			if (firstParent == secondParent) {
				return firstParent.indexOf(p1) - firstParent.indexOf(p2);
			}
			p1 = firstParent;
			p2 = secondParent;
		}
	}

	/**
	 * Return the string value of the node. The interpretation of this depends
	 * on the type of node. For an element it is the accumulated character
	 * content of the element, including descendant elements.
	 *
	 * @return the string value of the node
	 */

	public String getStringValue() {
        return node.getValue();
	}

    /**
     * Get the value of the item as a CharSequence. This is in some cases more efficient than
     * the version of the method that returns a String.
     */

    public CharSequence getStringValueCS() {
        return node.getValue();
    }

	/**
	 * Get name code. The name code is a coded form of the node name: two nodes
	 * with the same name code have the same namespace URI, the same local name,
	 * and the same prefix. By masking the name code with &0xfffff, you get a
	 * fingerprint: two nodes with the same fingerprint have the same local name
	 * and namespace URI.
	 *
	 * @see org.orbeon.saxon.om.NamePool#allocate allocate
	 */

	public int getNameCode() {
		switch (nodeKind) {
		case Type.ELEMENT:
		case Type.ATTRIBUTE:
		case Type.PROCESSING_INSTRUCTION:
			return docWrapper.getNamePool().allocate(getPrefix(), getURI(),
					getLocalPart());
		default:
			return -1;
		}
	}

	/**
	 * Get fingerprint. The fingerprint is a coded form of the expanded name of
	 * the node: two nodes with the same name code have the same namespace URI
	 * and the same local name. A fingerprint of -1 should be returned for a
	 * node with no name.
	 */

	public int getFingerprint() {
		int nc = getNameCode();
		if (nc == -1) return -1;
		return nc & 0xfffff;
	}

	/**
	 * Get the local part of the name of this node. This is the name after the
	 * ":" if any.
	 *
	 * @return the local part of the name. For an unnamed node, returns "".
	 */

	public String getLocalPart() {
		switch (nodeKind) {
			case Type.ELEMENT:
				return ((Element) node).getLocalName();
			case Type.ATTRIBUTE:
				return ((Attribute) node).getLocalName();
			case Type.PROCESSING_INSTRUCTION:
				return ((ProcessingInstruction) node).getTarget();
			default:
				return "";
		}
	}

    /**
     * Get the prefix of the name of the node. This is defined only for elements and attributes.
     * If the node has no prefix, or for other kinds of node, return a zero-length string.
     * @return The prefix of the name of the node.
     */

	public String getPrefix() {
		switch (nodeKind) {
			case Type.ELEMENT:
				return ((Element) node).getNamespacePrefix();
			case Type.ATTRIBUTE:
				return ((Attribute) node).getNamespacePrefix();
			default:
				return "";
		}
	}

    /**
	 * Get the URI part of the name of this node. This is the URI corresponding
	 * to the prefix, or the URI of the default namespace if appropriate.
	 *
	 * @return The URI of the namespace of this node. For an unnamed node, or
	 *         for a node with an empty prefix, return an empty string.
	 */

	public String getURI() {
		switch (nodeKind) {
			case Type.ELEMENT:
				return ((Element) node).getNamespaceURI();
			case Type.ATTRIBUTE:
				return ((Attribute) node).getNamespaceURI();
			default:
				return "";
		}
	}

	/**
	 * Get the display name of this node. For elements and attributes this is
	 * [prefix:]localname. For unnamed nodes, it is an empty string.
	 *
	 * @return The display name of this node. For a node with no name, return an
	 *         empty string.
	 */

	public String getDisplayName() {
		switch (nodeKind) {
			case Type.ELEMENT:
				return ((Element) node).getQualifiedName();
			case Type.ATTRIBUTE:
				return ((Attribute) node).getQualifiedName();
			case Type.PROCESSING_INSTRUCTION:
				return ((ProcessingInstruction) node).getTarget();
			default:
				return "";
		}
	}

	/**
	 * Get the NodeInfo object representing the parent of this node
	 */

	public NodeInfo getParent() {
		if (parent == null) {
			ParentNode p = node.getParent();
			if (p != null) parent = makeWrapper(p, docWrapper);
		}
		return parent;
	}

	/**
	 * Get the index position of this node among its siblings (starting from 0)
	 */

	public int getSiblingPosition() {
        // This method is used only to support generate-id()
        if (index != -1) return index;
		switch (nodeKind) {
			case Type.ATTRIBUTE: {
				Attribute att = (Attribute) node;
				Element p = (Element) att.getParent();
				if (p == null) return 0;
				for (int i=p.getAttributeCount(); --i >= 0;) {
					if (p.getAttribute(i) == att) {
						index = i;
						return i;
					}
				}
				throw new IllegalStateException("XOM node not linked to parent node");
			}

			default: {
				ParentNode p = node.getParent();
				int i = (p == null ? 0 : p.indexOf(node));
				if (i == -1) throw new IllegalStateException("XOM node not linked to parent node");
				index = i;
				return index;
			}
		}
	}

	/**
	 * Return an iteration over the nodes reached by the given axis from this
	 * node
	 *
	 * @param axisNumber
	 *            the axis to be used
	 * @return a SequenceIterator that scans the nodes reached by the axis in
	 *         turn.
	 */

	public AxisIterator iterateAxis(byte axisNumber) {
		return iterateAxis(axisNumber, AnyNodeTest.getInstance());
	}

	/**
	 * Return an iteration over the nodes reached by the given axis from this
	 * node
	 *
	 * @param axisNumber
	 *            the axis to be used
	 * @param nodeTest
	 *            A pattern to be matched by the returned nodes
	 * @return a SequenceIterator that scans the nodes reached by the axis in
	 *         turn.
	 */

	public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) {
		// for clarifications, see the W3C specs or:
		// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/xmlsdk/html/xmrefaxes.asp
		switch (axisNumber) {
		case Axis.ANCESTOR:
			return new AncestorAxisIterator(this, false, nodeTest);

		case Axis.ANCESTOR_OR_SELF:
			return new AncestorAxisIterator(this, true, nodeTest);

		case Axis.ATTRIBUTE:
			if (nodeKind != Type.ELEMENT || ((Element) node).getAttributeCount() == 0) {
				return EmptyIterator.getInstance();
			} else {
				return new AttributeAxisIterator(this, nodeTest);
			}

		case Axis.CHILD:
			if (hasChildNodes()) {
				return new ChildAxisIterator(this, true, true, nodeTest);
			} else {
				return EmptyIterator.getInstance();
			}

		case Axis.DESCENDANT:
			if (hasChildNodes()) {
				return new DescendantAxisIterator(this, false, false, nodeTest);
			} else {
				return EmptyIterator.getInstance();
			}

		case Axis.DESCENDANT_OR_SELF:
			if (hasChildNodes()) {
				return new DescendantAxisIterator(this, true, false, nodeTest);
			} else {
				return Navigator.filteredSingleton(this, nodeTest);
			}

		case Axis.FOLLOWING:
			if (getParent() == null) {
				return EmptyIterator.getInstance();
			} else {
				return new DescendantAxisIterator(this, false, true, nodeTest);
			}

		case Axis.FOLLOWING_SIBLING:
			if (nodeKind == Type.ATTRIBUTE || getParent() == null) {
				return EmptyIterator.getInstance();
			} else {
				return new ChildAxisIterator(this, false, true, nodeTest);
			}

		case Axis.NAMESPACE:
			if (nodeKind == Type.ELEMENT) {
				return NamespaceIterator.makeIterator(this, nodeTest);
			} else {
				return EmptyIterator.getInstance();
			}

		case Axis.PARENT:
			if (getParent() == null) {
				return EmptyIterator.getInstance();
			} else {
				return Navigator.filteredSingleton(getParent(), nodeTest);
			}

		case Axis.PRECEDING:
			return new PrecedingAxisIterator(this, false, nodeTest);
//			return new Navigator.AxisFilter(
//					new Navigator.PrecedingEnumeration(this, false), nodeTest);

		case Axis.PRECEDING_SIBLING:
			if (nodeKind == Type.ATTRIBUTE || getParent() == null) {
				return EmptyIterator.getInstance();
			} else {
				return new ChildAxisIterator(this, false, false, nodeTest);
			}

		case Axis.SELF:
			return Navigator.filteredSingleton(this, nodeTest);

		case Axis.PRECEDING_OR_ANCESTOR:
			// This axis is used internally by saxon for the xsl:number implementation,
			// it returns the union of the preceding axis and the ancestor axis.
			return new PrecedingAxisIterator(this, true, nodeTest);
//			return new Navigator.AxisFilter(new Navigator.PrecedingEnumeration(
//					this, true), nodeTest);

		default:
			throw new IllegalArgumentException("Unknown axis number " + axisNumber);
		}
	}

//	private static AxisIterator makeSingleIterator(NodeWrapper wrapper, NodeTest nodeTest) {
//		if (nodeTest == AnyNodeTest.getInstance() || nodeTest.matches(wrapper))
//			return SingletonIterator.makeIterator(wrapper);
//		else
//			return EmptyIterator.getInstance();
//	}

	/**
	 * Get the value of a given attribute of this node
	 *
	 * @param fingerprint
	 *            The fingerprint of the attribute name
	 * @return the attribute value if it exists or null if not
	 */

	public String getAttributeValue(int fingerprint) {
		if (nodeKind == Type.ELEMENT) {
			NamePool pool = docWrapper.getNamePool();
			String localName = pool.getLocalName(fingerprint);
			String uri = pool.getURI(fingerprint);
			Attribute att = ((Element) node).getAttribute(localName, uri);
			if (att != null) return att.getValue();
		}
		return null;
	}

    /**
     * Get the root node of the tree containing this node
     *
     * @return the NodeInfo representing the top-level ancestor of this node.
     *     This will not necessarily be a document node
     */

	public NodeInfo getRoot() {
		return docWrapper;
	}

    /**
     * Get the root node, if it is a document node.
     *
     * @return the DocumentInfo representing the containing document.
     */

	public DocumentInfo getDocumentRoot() {
		if (docWrapper.node instanceof Document) {
            return docWrapper;
        } else {
            return null;
        }
	}

	/**
	 * Determine whether the node has any children. <br />
	 * Note: the result is equivalent to <br />
	 * getEnumeration(Axis.CHILD, AnyNodeTest.getInstance()).hasNext()
	 */

	public boolean hasChildNodes() {
		return node.getChildCount() > 0;
	}

	/**
	 * Get a character string that uniquely identifies this node. Note:
	 * a.isSameNode(b) if and only if generateId(a)==generateId(b)
	 *
	 * @param buffer a buffer to contain a string that uniquely identifies this node, across all documents
     */

	public void generateId(FastStringBuffer buffer) {
        Navigator.appendSequentialKey(this, buffer, true);
		//buffer.append(Navigator.getSequentialKey(this));
	}

	/**
	 * Get the document number of the document containing this node. For a
	 * free-standing orphan node, just return the hashcode.
	 */

	public int getDocumentNumber() {
		return docWrapper.getDocumentNumber();
	}

	/**
	 * Copy this node to a given outputter (deep copy)
	 */

	public void copy(Receiver out, int whichNamespaces,
			boolean copyAnnotations, int locationId) throws XPathException {
		Navigator.copy(this, out, docWrapper.getNamePool(), whichNamespaces,
				copyAnnotations, locationId);
	}

    /**
     * Get all namespace undeclarations and undeclarations defined on this element.
     *
     * @param buffer If this is non-null, and the result array fits in this buffer, then the result
     *               may overwrite the contents of this array, to avoid the cost of allocating a new array on the heap.
     * @return An array of integers representing the namespace declarations and undeclarations present on
     *         this element. For a node other than an element, return null. Otherwise, the returned array is a
     *         sequence of namespace codes, whose meaning may be interpreted by reference to the name pool. The
     *         top half word of each namespace code represents the prefix, the bottom half represents the URI.
     *         If the bottom half is zero, then this is a namespace undeclaration rather than a declaration.
     *         The XML namespace is never included in the list. If the supplied array is larger than required,
     *         then the first unused entry will be set to -1.
     *         <p/>
     *         <p>For a node other than an element, the method returns null.</p>
     */

    public int[] getDeclaredNamespaces(int[] buffer) {
        if (node instanceof Element) {
            Element elem = (Element)node;
            int size = elem.getNamespaceDeclarationCount();
            if (size == 0) {
                return EMPTY_NAMESPACE_LIST;
            }
            int[] result = (buffer == null || size > buffer.length ? new int[size] : buffer);
            NamePool pool = getNamePool();
            for (int i=0; i < size; i++) {
                String prefix = elem.getNamespacePrefix(i);
                String uri = elem.getNamespaceURI(prefix);
                result[i] = pool.allocateNamespaceCode(prefix, uri);
            }
            if (size < result.length) {
                result[size] = -1;
            }
            return result;
        } else {
            return null;
        }
    }

    /**
     * Determine whether this node has the is-id property
     *
     * @return true if the node is an ID
     */

    public boolean isId() {
        return getNodeKind() == Type.ATTRIBUTE && ((Attribute)node).getType() == Attribute.Type.ID;
    }

    /**
     * Determine whether this node has the is-idref property
     *
     * @return true if the node is an IDREF or IDREFS element or attribute
     */

    public boolean isIdref() {
        return getNodeKind() == Type.ATTRIBUTE && (
                ((Attribute)node).getType() == Attribute.Type.IDREF ||
                ((Attribute)node).getType() == Attribute.Type.IDREFS);
    }

    /**
     * Determine whether the node has the is-nilled property
     *
     * @return true if the node has the is-nilled property
     */

    public boolean isNilled() {
        return false;
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Methods to support update access
    ///////////////////////////////////////////////////////////////////////////////

    /**
     * Delete this node (that is, detach it from its parent)
     */

    public void delete() throws XPathException {
        if (parent != null) {
            if (nodeKind == Type.ATTRIBUTE) {
                ((Element)parent.node).removeAttribute((Attribute)node);
            } else {
                ((ParentNode)parent.node).removeChild(node);
            }
        }
    }

    /**
     * Insert a sequence of nodes as the first children of the target node
     * @param content  the nodes to be inserted.
     */

//    public void insertAsFirst(SequenceIterator content) throws XPathException {
//        if (!(node instanceof ParentNode)) {
//            throw new XPathException("Cannot insert children unless parent is an element or document node");
//        }
//        int i = 0;
//        while (true) {
//            NodeInfo next = (NodeInfo)content.next();
//            if (next == null) {
//                break;
//            }
//            if (next instanceof NodeWrapper) {
//                Node nextNode = ((NodeWrapper)next).node;
//                ParentNode existingParent = nextNode.getParent();
//                if (existingParent != null) {
//                    existingParent.removeChild(nextNode);
//                }
//                ((ParentNode)node).insertChild(nextNode, i++);
//            } else {
//                throw new XPathException("Cannot insert non-XOM node");
//            }
//        }
//    }
//
//    /**
//     * Insert a sequence of nodes as the last children of the target node
//     * @param content  the nodes to be inserted.
//     */
//
//    public void insertAsLast(SequenceIterator content) throws XPathException {
//        if (!(node instanceof ParentNode)) {
//            throw new XPathException("Cannot insert children unless parent is an element or document node");
//        }
//        while (true) {
//            NodeInfo next = (NodeInfo)content.next();
//            if (next == null) {
//                break;
//            }
//            if (next instanceof NodeWrapper) {
//                Node nextNode = ((NodeWrapper)next).node;
//                ParentNode existingParent = nextNode.getParent();
//                if (existingParent != null) {
//                    existingParent.removeChild(nextNode);
//                }
//                ((ParentNode)node).appendChild(nextNode);
//            } else {
//                throw new XPathException("Cannot insert non-XOM node");
//            }
//        }
//    }
//
//
//    /**
//     * Add attributes to this node
//     *
//     * @param content the attributes to be added
//     */
//
//    public void insertAttributes(SequenceIterator content) throws XPathException {
//        if (nodeKind == Type.ELEMENT) {
//            while (true) {
//            NodeInfo next = (NodeInfo)content.next();
//            if (next == null) {
//                break;
//            }
//            if (next.getNodeKind() != Type.ATTRIBUTE) {
//                throw new XPathException("Node to be inserted is not an attribute");
//            }
//            if (next instanceof NodeWrapper) {
//                Node node = ((NodeWrapper)next).node;
//                if (node.getParent() != null) {
//                    node = node.copy();
//                }
//                ((Element)node).addAttribute((Attribute)node);
//            } else {
//                throw new XPathException("Cannot insert non-XOM node");
//            }
//        }
//        } else {
//            throw new XPathException("Cannot insert attributes unless parent is an element node");
//        }
//    }
//
//    /**
//     * Rename this node
//     *
//     * @param newName the new name
//     */
//
//    public void rename(StructuredQName newName) throws XPathException {
//        if (node instanceof Element) {
//            ((Element)node).setNamespaceURI(newName.getNamespaceURI());
//            ((Element)node).setLocalName(newName.getLocalName());
//            ((Element)node).setNamespacePrefix(newName.getPrefix());
//        } else if (node instanceof Attribute) {
//            ((Attribute)node).setNamespace(newName.getPrefix(), newName.getNamespaceURI());
//            ((Attribute)node).setLocalName(newName.getLocalName());
//        }
//    }

    /**
     * Replace this node with a given sequence of nodes
     *
     * @param replacement the replacement nodes
     */

//    public void replace(SequenceIterator replacement) throws XPathException {
//        NodeWrapper parentNode = ((NodeWrapper))
//        if (getPar) {
//            throw new XPathException("Cannot replace node unless parent is an element or document node");
//        }
//        while (true) {
//            NodeInfo next = (NodeInfo)content.next();
//            if (next == null) {
//                break;
//            }
//            if (next instanceof NodeWrapper) {
//                Node nextNode = ((NodeWrapper)next).node;
//                ParentNode existingParent = nextNode.getParent();
//                if (existingParent != null) {
//                    existingParent.removeChild(nextNode);
//                }
//                ((ParentNode)node).appendChild(nextNode);
//            } else {
//                throw new XPathException("Cannot insert non-XOM node");
//            }
//        }
//    }

    /**
     * Replace the string-value of this node
     *
     * @param stringValue the new string value
     */

//    public void replaceStringValue(CharSequence stringValue) throws XPathException {
//        switch (nodeKind) {
//            case Type.ATTRIBUTE:
//                ((Attribute)node).setValue(stringValue.toString());
//            case Type.
//        }
//    }

    ///////////////////////////////////////////////////////////////////////////////
	// Axis enumeration classes
	///////////////////////////////////////////////////////////////////////////////

    /**
	 * Handles the ancestor axis in a rather direct manner.
	 */
	private final class AncestorAxisIterator implements AxisIterator {

		private NodeWrapper start;
		private boolean includeSelf;

		private NodeInfo current;

		private NodeTest nodeTest;
		private int position;

		public AncestorAxisIterator(NodeWrapper start, boolean includeSelf, NodeTest test) {
			// use lazy instead of eager materialization (performance)
			this.start = start;
			if (test == AnyNodeTest.getInstance()) test = null;
            nodeTest = test;
			if (!includeSelf) {
                current = start;
            }
            this.includeSelf = includeSelf;
            position = 0;
		}

        /**
         * Move to the next node, without returning it. Returns true if there is
         * a next node, false if the end of the sequence has been reached. After
         * calling this method, the current node may be retrieved using the
         * current() function.
         */

        public boolean moveNext() {
            return (next() != null);
        }

		public Item next() {
			NodeInfo curr;
			do { // until we find a match
				curr = advance();
			}
			while (curr != null && nodeTest != null && (! nodeTest.matches(curr)));

			if (curr != null) position++;
			current = curr;
			return curr;
		}

		private NodeInfo advance() {
			if (current == null)
				current = start;
			else
				current = current.getParent();

			return current;
		}

		public Item current() {
			return current;
		}

		public int position() {
			return position;
		}

        public void close() {
        }

        /**
         * Return an iterator over an axis, starting at the current node.
         *
         * @param axis the axis to iterate over, using a constant such as
         *             {@link org.orbeon.saxon.om.Axis#CHILD}
         * @param test a predicate to apply to the nodes before returning them.
         * @throws NullPointerException if there is no current node
         */

        public AxisIterator iterateAxis(byte axis, NodeTest test) {
            return current.iterateAxis(axis, test);
        }

        /**
         * Return the atomized value of the current node.
         *
         * @return the atomized value.
         * @throws NullPointerException if there is no current node
         */

        public Value atomize() throws XPathException {
            return current.atomize();
        }

        /**
         * Return the string value of the current node.
         *
         * @return the string value, as an instance of CharSequence.
         * @throws NullPointerException if there is no current node
         */

        public CharSequence getStringValue() {
            return current.getStringValue();
        }

		public SequenceIterator getAnother() {
			return new AncestorAxisIterator(start, includeSelf, nodeTest);
		}

		public int getProperties() {
			return 0;
		}

	} // end of class AncestorAxisIterator

    /**
	 * Handles the attribute axis in a rather direct manner.
	 */
	private final class AttributeAxisIterator implements AxisIterator {

		private NodeWrapper start;

		private NodeInfo current;
		private int cursor;

		private NodeTest nodeTest;
		private int position;

		public AttributeAxisIterator(NodeWrapper start, NodeTest test) {
			// use lazy instead of eager materialization (performance)
			this.start = start;
			if (test == AnyNodeTest.getInstance()) test = null;
            nodeTest = test;
			position = 0;
			cursor = 0;
		}

        /**
         * Move to the next node, without returning it. Returns true if there is
         * a next node, false if the end of the sequence has been reached. After
         * calling this method, the current node may be retrieved using the
         * current() function.
         */

        public boolean moveNext() {
            return (next() != null);
        }


		public Item next() {
			NodeInfo curr;
			do { // until we find a match
				curr = advance();
			}
			while (curr != null && nodeTest != null && (! nodeTest.matches(curr)));

			if (curr != null) position++;
			current = curr;
			return curr;
		}

		private NodeInfo advance() {
			Element elem = (Element) start.node;
			if (cursor == elem.getAttributeCount()) return null;
			NodeInfo curr = makeWrapper(elem.getAttribute(cursor), docWrapper, start, cursor);
			cursor++;
			return curr;
		}

		public Item current() {
			return current;
		}

		public int position() {
			return position;
		}

        public void close() {
        }

        /**
          * Return an iterator over an axis, starting at the current node.
          *
          * @param axis the axis to iterate over, using a constant such as
          *             {@link org.orbeon.saxon.om.Axis#CHILD}
          * @param test a predicate to apply to the nodes before returning them.
          * @throws NullPointerException if there is no current node
          */

         public AxisIterator iterateAxis(byte axis, NodeTest test) {
             return current.iterateAxis(axis, test);
         }

         /**
          * Return the atomized value of the current node.
          *
          * @return the atomized value.
          * @throws NullPointerException if there is no current node
          */

         public Value atomize() throws XPathException {
             return current.atomize();
         }

         /**
          * Return the string value of the current node.
          *
          * @return the string value, as an instance of CharSequence.
          * @throws NullPointerException if there is no current node
          */

         public CharSequence getStringValue() {
             return current.getStringValue();
         }

		public SequenceIterator getAnother() {
			return new AttributeAxisIterator(start, nodeTest);
		}

 		public int getProperties() {
			return 0;
		}

	} // end of class AttributeAxisIterator

    /**
	 * The class ChildAxisIterator handles not only the child axis, but also the
	 * following-sibling and preceding-sibling axes. It can also iterate the
	 * children of the start node in reverse order, something that is needed to
	 * support the preceding and preceding-or-ancestor axes (the latter being
	 * used by xsl:number)
	 */
	private final class ChildAxisIterator implements AxisIterator {

		private NodeWrapper start;
		private NodeWrapper commonParent;
		private int ix;
		private boolean downwards; // iterate children of start node (not siblings)
		private boolean forwards; // iterate in document order (not reverse order)

		private NodeInfo current;
		private ParentNode par;
		private int cursor;

		private NodeTest nodeTest;
		private int position;

		private ChildAxisIterator(NodeWrapper start, boolean downwards, boolean forwards, NodeTest test) {
			this.start = start;
			this.downwards = downwards;
			this.forwards = forwards;

			if (test == AnyNodeTest.getInstance()) test = null;
			nodeTest = test;
			position = 0;

            commonParent = downwards ? start : (NodeWrapper)start.getParent();

            par = (ParentNode) commonParent.node;
			if (downwards) {
				ix = (forwards ? 0 : par.getChildCount());
			} else {
				// find the start node among the list of siblings
//				ix = start.getSiblingPosition();
				ix = par.indexOf(start.node);
				if (forwards) ix++;
			}
			cursor = ix;
			if (!downwards && !forwards) ix--;
		}

        /**
         * Move to the next node, without returning it. Returns true if there is
         * a next node, false if the end of the sequence has been reached. After
         * calling this method, the current node may be retrieved using the
         * current() function.
         */

        public boolean moveNext() {
            return (next() != null);
        }


		public Item next() {
			NodeInfo curr;
			do { // until we find a match
				curr = advance();
			}
			while (curr != null && nodeTest != null && (! nodeTest.matches(curr)));

			if (curr != null) position++;
			current = curr;
			return curr;
		}

		private NodeInfo advance() {
			Node nextChild;
			do {
				if (forwards) {
					if (cursor == par.getChildCount()) return null;
					nextChild = par.getChild(cursor++);
				} else { // backwards
					if (cursor == 0) return null;
					nextChild = par.getChild(--cursor);
				}
			} while (nextChild instanceof DocType);
			// DocType is not an XPath node; can occur for /child::node()

			NodeInfo curr = makeWrapper(nextChild, docWrapper, commonParent, ix);
			ix += (forwards ? 1 : -1);
			return curr;
		}

		public Item current() {
			return current;
		}

		public int position() {
			return position;
		}

        public void close() {
        }

        /**
          * Return an iterator over an axis, starting at the current node.
          *
          * @param axis the axis to iterate over, using a constant such as
          *             {@link org.orbeon.saxon.om.Axis#CHILD}
          * @param test a predicate to apply to the nodes before returning them.
          * @throws NullPointerException if there is no current node
          */

         public AxisIterator iterateAxis(byte axis, NodeTest test) {
             return current.iterateAxis(axis, test);
         }

         /**
          * Return the atomized value of the current node.
          *
          * @return the atomized value.
          * @throws NullPointerException if there is no current node
          */

         public Value atomize() throws XPathException {
             return current.atomize();
         }

         /**
          * Return the string value of the current node.
          *
          * @return the string value, as an instance of CharSequence.
          * @throws NullPointerException if there is no current node
          */

         public CharSequence getStringValue() {
             return current.getStringValue();
         }

		public SequenceIterator getAnother() {
			return new ChildAxisIterator(start, downwards, forwards, nodeTest);
		}

 		public int getProperties() {
			return 0;
		}
	}

	/**
	 * A bit of a misnomer; efficiently takes care of descendants,
	 * descentants-or-self as well as "following" axis.
	 * "includeSelf" must be false for the following axis.
	 * Uses simple and effective O(1) backtracking via indexOf().
	 */
    private final class DescendantAxisIterator implements AxisIterator {

		private NodeWrapper start;
		private boolean includeSelf;
		private boolean following;

		private Node anchor; // so we know where to stop the scan
		private Node currNode;
		private boolean moveToNextSibling;

		private NodeInfo current;
		private NodeTest nodeTest;
		private int position;

		private String testLocalName;
		private String testURI;

		public DescendantAxisIterator(NodeWrapper start, boolean includeSelf, boolean following, NodeTest test) {
			this.start = start;
			this.includeSelf = includeSelf;
			this.following = following;
			moveToNextSibling = following;

			if (!following) anchor = start.node;
			if (!includeSelf) currNode = start.node;

			if (test == AnyNodeTest.getInstance()) { // performance hack
				test = null; // mark as AnyNodeTest
			}
			else if (test instanceof NameTest) {
				NameTest nt = (NameTest) test;
				if (nt.getPrimitiveType() == Type.ELEMENT) { // performance hack
					// mark as element name test
					NamePool pool = getNamePool();
					testLocalName = pool.getLocalName(nt.getFingerprint());
					testURI = pool.getURI(nt.getFingerprint());
				}
			}
			else if (test instanceof NodeKindTest) {
				if (test.getPrimitiveType() == Type.ELEMENT) { // performance hack
					// mark as element type test
					testLocalName = "";
					testURI = null;
				}
			}
			nodeTest = test;
			position = 0;
		}

        /**
         * Move to the next node, without returning it. Returns true if there is
         * a next node, false if the end of the sequence has been reached. After
         * calling this method, the current node may be retrieved using the
         * current() function.
         */

        public boolean moveNext() {
            return (next() != null);
        }


		public Item next() {
			NodeInfo curr;
			do { // until we find a match
				curr = advance();
			}
			while (curr != null && nodeTest != null && (! nodeTest.matches(curr)));

			if (curr != null) position++;
			current = curr;
			return curr;
		}

		// might look expensive at first glance - but it's not
		private NodeInfo advance() {
			if (currNode == null) { // if includeSelf
				currNode = start.node;
				return start;
			}

			int i;
			do {
				i = 0;
				Node p = currNode;

				if (p.getChildCount() == 0 || moveToNextSibling) { // move to next sibling

					moveToNextSibling = false; // do it just once
					while (true) {
						// if we've reached the root we're done scanning
						p = currNode.getParent();
						if (p == null) return null;

						// Note: correct even if currNode is an attribute.
						// Performance is particularly good with the O(1) patch
						// for XOM's ParentNode.indexOf()
						i = currNode.getParent().indexOf(currNode) + 1;

						if (i < p.getChildCount()) {
							break; // break out of while(true) loop; move to next sibling
						}
						else { // reached last sibling; move up
							currNode = p;
							// if we've come all the way back to the start anchor we're done
							if (p == anchor) return null;
						}
					}
				}
				currNode = p.getChild(i);
			} while (!conforms(currNode));

			// note the null here: makeNodeWrapper(parent, ...) is fast, so it
			// doesn't really matter that we don't keep a link to it.
			// In fact, it makes objects more short lived, easing pressure on
			// the VM allocator and collector for tenured heaps.
			return makeWrapper(currNode, docWrapper, null, i);
		}

		// avoids NodeWrapper allocation when there's clearly a mismatch (common case)
		private boolean conforms(Node node) {
			if (testLocalName != null) { // element test?
				if (!(node instanceof Element)) return false;
				if (testURI == null) return true; // pure element type test

				// element name test
				Element elem = (Element) node;
				return testLocalName.equals(elem.getLocalName()) &&
					testURI.equals(elem.getNamespaceURI());
			}
			else { // DocType is not an XPath node; can occur for /descendants::node()
				return !(node instanceof DocType);
			}
		}

		public Item current() {
			return current;
		}

		public int position() {
			return position;
		}

        public void close() {
        }

        /**
          * Return an iterator over an axis, starting at the current node.
          *
          * @param axis the axis to iterate over, using a constant such as
          *             {@link org.orbeon.saxon.om.Axis#CHILD}
          * @param test a predicate to apply to the nodes before returning them.
          * @throws NullPointerException if there is no current node
          */

         public AxisIterator iterateAxis(byte axis, NodeTest test) {
             return current.iterateAxis(axis, test);
         }

         /**
          * Return the atomized value of the current node.
          *
          * @return the atomized value.
          * @throws NullPointerException if there is no current node
          */

         public Value atomize() throws XPathException {
             return current.atomize();
         }

         /**
          * Return the string value of the current node.
          *
          * @return the string value, as an instance of CharSequence.
          * @throws NullPointerException if there is no current node
          */

         public CharSequence getStringValue() {
             return current.getStringValue();
         }

		public SequenceIterator getAnother() {
			return new DescendantAxisIterator(start, includeSelf, following, nodeTest);
		}

 		public int getProperties() {
			return 0;
		}
	}

	/**
	 * Efficiently takes care of preceding axis and Saxon internal preceding-or-ancestor axis.
	 * Uses simple and effective O(1) backtracking via indexOf().
	 * Implemented along similar lines as DescendantAxisIterator.
	 */
    private final class PrecedingAxisIterator implements AxisIterator {

		private NodeWrapper start;
		private boolean includeAncestors;

		private Node currNode;
		private ParentNode nextAncestor; // next ancestors to skip if !includeAncestors

		private NodeInfo current;
		private NodeTest nodeTest;
		private int position;

		private String testLocalName;
		private String testURI;

		public PrecedingAxisIterator(NodeWrapper start, boolean includeAncestors, NodeTest test) {
			this.start = start;
			this.includeAncestors = includeAncestors;
			currNode = start.node;
            nextAncestor = includeAncestors ? null : start.node.getParent();

            if (test == AnyNodeTest.getInstance()) { // performance hack
				test = null; // mark as AnyNodeTest
			}
			else if (test instanceof NameTest) {
				NameTest nt = (NameTest) test;
				if (nt.getPrimitiveType() == Type.ELEMENT) { // performance hack
					// mark as element name test
					NamePool pool = getNamePool();
					testLocalName = pool.getLocalName(nt.getFingerprint());
					testURI = pool.getURI(nt.getFingerprint());
				}
			}
			else if (test instanceof NodeKindTest) {
				if (test.getPrimitiveType() == Type.ELEMENT) { // performance hack
					// mark as element type test
					testLocalName = "";
					testURI = null;
				}
			}
			nodeTest = test;
			position = 0;
		}

        /**
         * Move to the next node, without returning it. Returns true if there is
         * a next node, false if the end of the sequence has been reached. After
         * calling this method, the current node may be retrieved using the
         * current() function.
         */

        public boolean moveNext() {
            return (next() != null);
        }

		public Item next() {
			NodeInfo curr;
			do { // until we find a match
				curr = advance();
			}
			while (curr != null && nodeTest != null && (! nodeTest.matches(curr)));

			if (curr != null) position++;
			current = curr;
			return curr;
		}

		// might look expensive at first glance - but it's not
		private NodeInfo advance() {
			int i;
			do {
				Node p;

				while (true) {
					// if we've reached the root we're done scanning
//					System.out.println("p="+p);
					p = currNode.getParent();
					if (p == null) return null;

					// Note: correct even if currNode is an attribute.
					// Performance is particularly good with the O(1) patch
					// for XOM's ParentNode.indexOf()
					i = currNode.getParent().indexOf(currNode) - 1;

					if (i >= 0) { // move to next sibling's last descendant node
						p = p.getChild(i); // move to next sibling
						int j;
						while ((j = p.getChildCount()-1) >= 0) { // move to last descendant node
							p = p.getChild(j);
							i = j;
						}
						break; // break out of while(true) loop
					}
					else { // there are no more siblings; move up
						// if !includeAncestors skip the ancestors of the start node
						// assert p != null
						if (p != nextAncestor) break; // break out of while(true) loop

						nextAncestor = nextAncestor.getParent();
						currNode = p;
					}
				}
				currNode = p;

			} while (!conforms(currNode));

			// note the null here: makeNodeWrapper(parent, ...) is fast, so it
			// doesn't really matter that we don't keep a link to it.
			// In fact, it makes objects more short lived, easing pressure on
			// the VM allocator and collector for tenured heaps.
			return makeWrapper(currNode, docWrapper, null, i);
		}

		// avoids NodeWrapper allocation when there's clearly a mismatch (common case)
		// same as for DescendantAxisIterator
		private boolean conforms(Node node) {
			if (testLocalName != null) { // element test?
				if (!(node instanceof Element)) {
                    return false;
                }
				if (testURI == null) {
                    return true; // pure element type test
                }

				// element name test
				Element elem = (Element) node;
				return testLocalName.equals(elem.getLocalName()) &&
					testURI.equals(elem.getNamespaceURI());
			}
			else { // DocType is not an XPath node
				return !(node instanceof DocType);
			}
		}

		public Item current() {
			return current;
		}

		public int position() {
			return position;
		}

        public void close() {
        }

        /**
          * Return an iterator over an axis, starting at the current node.
          *
          * @param axis the axis to iterate over, using a constant such as
          *             {@link org.orbeon.saxon.om.Axis#CHILD}
          * @param test a predicate to apply to the nodes before returning them.
          * @throws NullPointerException if there is no current node
          */

         public AxisIterator iterateAxis(byte axis, NodeTest test) {
             return current.iterateAxis(axis, test);
         }

         /**
          * Return the atomized value of the current node.
          *
          * @return the atomized value.
          * @throws NullPointerException if there is no current node
          */

         public Value atomize() throws XPathException {
             return current.atomize();
         }

         /**
          * Return the string value of the current node.
          *
          * @return the string value, as an instance of CharSequence.
          * @throws NullPointerException if there is no current node
          */

         public CharSequence getStringValue() {
             return current.getStringValue();
         }

		public SequenceIterator getAnother() {
			return new PrecedingAxisIterator(start, includeAncestors, nodeTest);
		}

 		public int getProperties() {
			return 0;
		}
	}

}

//
// The contents of this file are subject to the Mozilla Public License Version
// 1.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.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is: all this file.
//
// The Initial Developer of the Original Code is Michael Kay, with extensive
// rewriting by Wolfgang Hoschek
//
// Portions created by (your name) are Copyright (C) (your legal entity). All
// Rights Reserved.
//
// Contributor(s): none.
//
