Package org.exist.dom

Source Code of org.exist.dom.AbstractNodeSet

/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*  $Id$
*/
package org.exist.dom;

import org.apache.log4j.Logger;
import org.exist.collections.Collection;
import org.exist.numbering.NodeId;
import org.exist.storage.DBBroker;
import org.exist.xquery.Constants;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.AbstractSequence;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.MemoryNodeSet;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.Type;
import org.w3c.dom.Node;

import java.util.Iterator;

/**
* Abstract base class for all node set implementations. A node set is a special type of sequence,
* which contains only nodes. Class NodeSet thus implements the {@link org.exist.xquery.value.Sequence}
* as well as the DOM {@link org.w3c.dom.NodeList} interfaces.
*
* Please note that a node set may or may not contain duplicate nodes. Some implementations
* (e.g. {@link org.exist.dom.ExtArrayNodeSet}) remove duplicates when sorting the set.
*/
public abstract class AbstractNodeSet extends AbstractSequence implements NodeSet {

    protected final static Logger LOG = Logger.getLogger(AbstractNodeSet.class);

    // indicates the type of an optional value index that may have
    // been defined on the nodes in this set.
    protected int indexType = Type.ANY_TYPE;

    private boolean isCached = false;

    private boolean processInReverseOrder = false;

    private boolean trackMatches = true;

    protected AbstractNodeSet() {
        isEmpty = true;
    }

    /**
     * Return an iterator on the nodes in this list. The iterator returns nodes
     * according to the internal ordering of nodes (i.e. level first), not in document-
     * order.
     *
     */
    public abstract NodeSetIterator iterator();

    /* (non-Javadoc)
     * @see org.exist.xquery.value.Sequence#iterate()
     */
    @Override
    public abstract SequenceIterator iterate() throws XPathException;

    /* (non-Javadoc)
     * @see org.exist.xquery.value.Sequence#unorderedIterator()
     */
    @Override
    public abstract SequenceIterator unorderedIterator() throws XPathException;

    /* (non-Javadoc)
     * @see org.exist.xquery.value.Sequence#getItemType()
     */
    @Override
    public int getItemType() {
        return Type.NODE;
    }

    /**
     * Check if this node set contains a node matching the document and
     * node-id of the given NodeProxy object.
     *
     * @param proxy
     */
    public abstract boolean contains(NodeProxy proxy);

    /**
     * Add a new proxy object to the node set. Please note: node set
     * implementations may allow duplicates.
     *
     * @param proxy
     */
    public abstract void add(NodeProxy proxy);

    /**
     * Add a proxy object to the node set. The sizeHint parameter
     * gives a hint about the number of items to be expected for the
     * current document.
     *
     * @param proxy
     * @param sizeHint
     */
    public void add(NodeProxy proxy, int sizeHint) {
        add(proxy);
    }

    /**
     * Add a sequence item to the node set. The item has to be
     * a subtype of node.
     */
    @Override
    public void add(Item item) throws XPathException {
        if (!Type.subTypeOf(item.getType(), Type.NODE))
            {throw new XPathException("item has wrong type");}
        add((NodeProxy) item);
    }

    /**
     * Add all items from the given sequence to the node set. All items
     * have to be a subtype of node.
     *
     * @param other
     * @throws XPathException
     */
    @Override
    public void addAll(Sequence other) throws XPathException {
        if (!other.isEmpty() && !Type.subTypeOf(other.getItemType(), Type.NODE))
            {throw new XPathException("sequence argument is not a node sequence");}
        if (Type.subTypeOf(other.getItemType(), Type.NODE))
          {addAll((NodeSet) other);}
        for (final SequenceIterator i = other.iterate(); i.hasNext();) {
            add(i.nextItem());
        }
    }

    /**
     * Add all nodes from the given node set.
     *
     * @param other
     */
    public abstract void addAll(NodeSet other);

    /**
     * Return the number of nodes contained in this node set.
     */
    public abstract int getLength();

    public NodeSet copy() {
        final NewArrayNodeSet set = new NewArrayNodeSet(getLength());
        set.addAll(this);
        return set;
    }

  @Override
    public void setIsCached(boolean cached) {
        isCached = cached;
    }

    @Override
    public boolean isCached() {
        return isCached;
    }

    /* (non-Javadoc)
     * @see org.exist.xquery.value.Sequence#removeDuplicates()
     */
    public void removeDuplicates() {
        // all instances of NodeSet will automatically remove duplicates
        // upon a call to getLength() or iterate()
    }

    public abstract Node item(int pos);

    /**
     * Get the node at position pos within this node set.
     * @param pos
     */
    public abstract NodeProxy get(int pos);

    /**
     * Get a node from this node set matching the document and node id of
     * the given NodeProxy.
     *
     * @param p
     */
    public abstract NodeProxy get(NodeProxy p);

    @Override
    public DocumentSet getDocumentSet() {
        final MutableDocumentSet ds = new DefaultDocumentSet();
        NodeProxy p;
        for(final Iterator<NodeProxy> i = iterator(); i.hasNext(); ) {
            p = i.next();
            ds.add(p.getDocument());
        }
        return ds;
    }

    @Override
    public Iterator<Collection> getCollectionIterator() {
        return new CollectionIterator();
    }

    /**
     * Check if any child nodes are found within this node set for a given
     * set of potential parent nodes.
     *
     * If mode is {@link #DESCENDANT}, the returned node set will contain
     * all child nodes found in this node set for each parent node. If mode is
     * {@link #ANCESTOR}, the returned set will contain those parent nodes,
     * for which children have been found.
     *
     * @param al a node set containing potential parent nodes
     * @param mode selection mode
     */
    public NodeSet selectParentChild(NodeSet al, int mode) {
        return selectParentChild(al, mode, Expression.NO_CONTEXT_ID);
    }

    /**
     * Check if any child nodes are found within this node set for a given
     * set of potential ancestor nodes.
     *
     * If mode is {@link #DESCENDANT}, the returned node set will contain
     * all child nodes found in this node set for each parent node. If mode is
     * {@link #ANCESTOR}, the returned set will contain those parent nodes,
     * for which children have been found.
     *
     * @param al a node set containing potential parent nodes
     * @param mode selection mode
     * @param contextId used to track context nodes when evaluating predicate
     * expressions. If contextId != {@link Expression#NO_CONTEXT_ID}, the current context
     * will be added to each result of the of the selection.
     */
    public NodeSet selectParentChild(NodeSet al, int mode, int contextId) {
        return NodeSetHelper.selectParentChild(this, al, mode, contextId);
    }

    public boolean matchParentChild(NodeSet al, int mode, int contextId) {
        return NodeSetHelper.matchParentChild(this, al, mode, contextId);
    }

    /**
     * Check if any descendant nodes are found within this node set for a given
     * set of potential ancestor nodes.
     *
     * If mode is {@link #DESCENDANT}, the returned node set will contain
     * all descendant nodes found in this node set for each ancestor. If mode is
     * {@link #ANCESTOR}, the returned set will contain those ancestor nodes,
     * for which descendants have been found.
     *
     * @param al a node set containing potential parent nodes
     * @param mode selection mode
     * @param includeSelf if true, check if the ancestor node itself is contained in
     * the set of descendant nodes (descendant-or-self axis)
     * @param contextId used to track context nodes when evaluating predicate
     * expressions. If contextId != {@link Expression#NO_CONTEXT_ID}, the current context
     * will be added to each result of the selection.
     *
     */
    public NodeSet selectAncestorDescendant(NodeSet al,  int mode, boolean includeSelf,
    int contextId, boolean copyMatches) {
        return NodeSetHelper.selectAncestorDescendant(this, al, mode, includeSelf, contextId);
    }

    public boolean matchAncestorDescendant(NodeSet al,  int mode, boolean includeSelf,
            int contextId, boolean copyMatches) {
        return NodeSetHelper.matchAncestorDescendant(this, al, mode, includeSelf, contextId);
    }

    /**
     * For a given set of potential ancestor nodes, return all ancestors
     * having descendants in this node set.
     *
     * @param  descendants    node set containing potential ancestors
     * @param includeSelf if true, check if the ancestor node itself is contained
     * in this node set (ancestor-or-self axis)
     * @param contextId
     */
    public NodeSet selectAncestors(NodeSet descendants, boolean includeSelf, int contextId) {
        return NodeSetHelper.selectAncestors(this, descendants, includeSelf, contextId);
    }

    public boolean matchAncestors(NodeSet descendants, boolean includeSelf, int contextId) {
        return NodeSetHelper.matchAncestors(this, descendants, includeSelf, contextId);
    }

    public NodeSet selectFollowing(NodeSet fl, int contextId) throws XPathException {
        return NodeSetHelper.selectFollowing(fl, this);
    }

    public NodeSet selectFollowing(NodeSet following, int position, int contextId) throws XPathException {
        throw new UnsupportedOperationException();
    }

    public NodeSet selectPreceding(NodeSet pl, int contextId) throws XPathException {
        return NodeSetHelper.selectPreceding(pl, this);
    }

    public NodeSet selectPreceding(NodeSet preceding, int nth, int contextId) throws XPathException, UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }

    /**
     * Select all nodes from the passed node set, which
     * are preceding or following siblings of the nodes in
     * this set. If mode is {@link #FOLLOWING}, only nodes following
     * the context node are selected. {@link #PRECEDING} selects
     * preceding nodes.
     *
     * @param siblings a node set containing potential siblings
     * @param contextId used to track context nodes when evaluating predicate
     * expressions. If contextId != {@link Expression#NO_CONTEXT_ID}, the current context
     * will be added to each result of the of the selection.
     */
    public NodeSet selectPrecedingSiblings(NodeSet siblings, int contextId) {
        return NodeSetHelper.selectPrecedingSiblings(this, siblings, contextId);
    }

    public NodeSet selectFollowingSiblings(NodeSet siblings, int contextId) {
        return NodeSetHelper.selectFollowingSiblings(this, siblings, contextId);
    }

    public NodeSet directSelectAttribute(DBBroker broker, org.exist.xquery.NodeTest qname, int contextId) {
        return NodeSetHelper.directSelectAttributes(broker, this, qname, contextId);
    }

    public boolean directMatchAttribute(DBBroker broker, org.exist.xquery.NodeTest qname, int contextId) {
        return NodeSetHelper.directMatchAttributes(broker, this, qname, contextId);
    }

    public NodeProxy parentWithChild(DocumentImpl doc, NodeId nodeId, boolean directParent, boolean includeSelf) {
        NodeProxy temp = get(doc, nodeId);
        if (includeSelf && temp != null)
            {return temp;}
        nodeId = nodeId.getParentId();
        while (nodeId != null) {
            temp = get(doc, nodeId);
            if (temp != null)
                {return temp;}
            else if (directParent)
                {return null;}
            nodeId = nodeId.getParentId();
        }
        return null;
    }

    /**
     * Check if the given node has an ancestor contained in this node set
     * and return the ancestor found.
     *
     * If directParent is true, only immediate ancestors (parents) are considered.
     * Otherwise the method will call itself recursively for all the node's
     * parents.
     *
     * If includeSelf is true, the method returns also true if
     * the node itself is contained in the node set.
     */
    public NodeProxy parentWithChild(NodeProxy proxy, boolean directParent,  boolean includeSelf, int level) {
        return parentWithChild(proxy.getDocument(), proxy.getNodeId(), directParent, includeSelf);
    }

    /**
     * Return a new node set containing the parent nodes of all nodes in the
     * current set.
     * @param contextId an <code>int</code> value
     * @return a <code>NodeSet</code> value
     */
    public NodeSet getParents(int contextId) {
        final NodeSet parents = new NewArrayNodeSet();
        NodeProxy parent = null;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            final NodeProxy current = i.next();
            final NodeId parentID = current.getNodeId().getParentId();
            if (parentID != null && !(parentID.getTreeLevel() == 1 &&
                    current.getDocument().getCollection().isTempCollection())) {
                if (parent == null || parent.getDocument().getDocId() !=
                        current.getDocument().getDocId() || !parent.getNodeId().equals(parentID)) {
                    if (parentID != NodeId.DOCUMENT_NODE) {
                        parent = new NodeProxy(current.getDocument(), parentID, Node.ELEMENT_NODE,
                            StoredNode.UNKNOWN_NODE_IMPL_ADDRESS);
                    } else {
                        parent = new NodeProxy(current.getDocument(), parentID, Node.DOCUMENT_NODE,
                            StoredNode.UNKNOWN_NODE_IMPL_ADDRESS);
                    }
                }
                if (Expression.NO_CONTEXT_ID != contextId) {
                    parent.addContextNode(contextId, current);
                } else {
                    parent.copyContext(current);
                }
                parent.addMatches(current);
                parents.add(parent);
            }
        }
        return parents;
    }

    /**
     * The method <code>getAncestors</code>
     *
     * @param contextId an <code>int</code> value
     * @param includeSelf a <code>boolean</code> value
     * @return a <code>NodeSet</code> value
     */
    public NodeSet getAncestors(int contextId, boolean includeSelf) {
        final ExtArrayNodeSet ancestors = new ExtArrayNodeSet();
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            final NodeProxy current = i.next();
            if (includeSelf) {
                if (Expression.NO_CONTEXT_ID != contextId)
                    {current.addContextNode(contextId, current);}
                ancestors.add(current);
            }
            NodeId parentID = current.getNodeId().getParentId();
            while (parentID != null) {
                //Filter out the temporary nodes wrapper element
                if (parentID != NodeId.DOCUMENT_NODE &&
                    !(parentID.getTreeLevel() == && current.getDocument().getCollection().isTempCollection())) {
                    final NodeProxy parent = new NodeProxy(current.getDocument(), parentID, Node.ELEMENT_NODE);
                    if (Expression.NO_CONTEXT_ID != contextId)
                        {parent.addContextNode(contextId, current);}
                    else
                        {parent.copyContext(current);}
                    ancestors.add(parent);
                }
                parentID = parentID.getParentId();
            }
        }
        ancestors.mergeDuplicates();
        return ancestors;
    }

    /**
     * Get a hint about how many nodes in this node set belong to the
     * specified document. This is just used for allocating new node sets.
     * The information does not need to be exact. -1 is returned if the
     * size cannot be determined (the default).
     *
     * @param doc
     */
    public int getSizeHint(DocumentImpl doc) {
        return Constants.NO_SIZE_HINT;
    }

    /**
     * Return a new node set, which represents the intersection of the current
     * node set with the given node set.
     *
     * @param other
     */
    public NodeSet intersection(NodeSet other) {
        final AVLTreeNodeSet r = new AVLTreeNodeSet();
        NodeProxy l, p;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            l = i.next();
            if ((p = other.get(l)) != null) {
                l.addMatches(p);
                r.add(l);
            }
        }
        return r;
    }

    public NodeSet deepIntersection(NodeSet other) {
        final AVLTreeNodeSet r = new AVLTreeNodeSet();
        NodeProxy l, p, q;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            l = i.next();
            if ((p = other.parentWithChild(l, false, true, NodeProxy.UNKNOWN_NODE_LEVEL)) != null) {
                if (p.getNodeId().equals(l.getNodeId()))
                    {p.addMatches(l);}
                r.add(p);
            }
        }
        for (final Iterator<NodeProxy> i = other.iterator(); i.hasNext();) {
            l = i.next();
            if ((q = parentWithChild(l, false, true, NodeProxy.UNKNOWN_NODE_LEVEL)) != null) {
                if ((p = r.get(q)) != null) {
                    p.addMatches(l);
                } else
                    {r.add(l);}
            }
        }
        return r;
    }

    public NodeSet except(NodeSet other) {
        final AVLTreeNodeSet r = new AVLTreeNodeSet();
        NodeProxy l;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            l = i.next();
            if (!other.contains(l)) {
                r.add(l);
            }
        }
        return r;
    }

    public NodeSet filterDocuments(NodeSet otherSet) {
        final DocumentSet docs = otherSet.getDocumentSet();
        final NodeSet newSet = new NewArrayNodeSet();
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            final NodeProxy p = i.next();
            if (docs.contains(p.getDocument().getDocId()))
                {newSet.add(p);}
        }
        return newSet;
    }

    public void setProcessInReverseOrder(boolean inReverseOrder) {
        processInReverseOrder = inReverseOrder;
    }

    public boolean getProcessInReverseOrder() {
        return processInReverseOrder;
    }

    /**
     * Return a new node set which represents the union of the
     * current node set and the given node set.
     *
     * @param other
     */
    public NodeSet union(NodeSet other) {
        if (isEmpty())
            {return other;}
        if (other.isEmpty())
            {return this;}
        final NewArrayNodeSet result = new NewArrayNodeSet();
        result.addAll(other);
        NodeProxy p, c;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            p = i.next();
            if ((c = other.get(p)) != null) {
                c.addMatches(p);
            } else
                {result.add(p);}
        }
        return result;
    }

    /**
     * Returns all context nodes associated with the nodes in
     * this node set.
     *
     * @param contextId used to track context nodes when evaluating predicate
     * expressions. If contextId != {@link Expression#NO_CONTEXT_ID}, the current context
     * will be added to each result of the of the selection.
     */
    public NodeSet getContextNodes(int contextId) {
        NodeProxy current, context;
        ContextItem contextNode;
        final NewArrayNodeSet result = new NewArrayNodeSet();
        DocumentImpl lastDoc = null;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
            current = i.next();
            contextNode = current.getContext();
            while (contextNode != null) {
                if (contextNode.getContextId() == contextId) {
                    context = contextNode.getNode();
                    context.addMatches(current);
                    if (Expression.NO_CONTEXT_ID != contextId)
                        {context.addContextNode(contextId, context);}
                    if (lastDoc != null && lastDoc.getDocId() != context.getDocument().getDocId()) {
                        lastDoc = context.getDocument();
                        result.add(context, getSizeHint(lastDoc));
                    } else
                        {result.add(context);}
                }
                contextNode = contextNode.getNextDirect();
            }
        }
        return result;
    }

    /**
     * Always returns this.
     *
     * @see org.exist.xquery.value.Sequence#toNodeSet()
     */
    public NodeSet toNodeSet() throws XPathException {
        return this;
    }

    public MemoryNodeSet toMemNodeSet() throws XPathException {
        return null;
    }

    /* (non-Javadoc)
     * @see org.exist.dom.NodeSet#getState()
     */
    @Override
    public int getState() {
        return 1;
    }

    /* (non-Javadoc)
     * @see org.exist.dom.NodeSet#hasChanged(int)
     */
    @Override
    public boolean hasChanged(int previousState) {
        return false;
    }

    @Override
    public void setTrackMatches(boolean track) {
      this.trackMatches = track;
    }

    @Override
    public boolean getTrackMatches() {
        return trackMatches;
    }

    /**
     * If all nodes in this set have an index, returns the common
     * supertype used to build the index, e.g. xs:integer or xs:string.
     * If the nodes have different index types or no node has been indexed,
     * returns {@link Type#ITEM}.
     *
     * @see org.exist.xquery.GeneralComparison
     * @see org.exist.xquery.ValueComparison
     */
    public int getIndexType() {
        //Is the index type initialized ?
        if (indexType == Type.ANY_TYPE) {
            for (final Iterator<NodeProxy> i = iterator(); i.hasNext();) {
                final NodeProxy node = i.next();
                if (node.getDocument().getCollection().isTempCollection()) {
                    //Temporary nodes return default values
                    indexType = Type.ITEM;
                    break;
                }
                int nodeIndexType = node.getIndexType();
                //Refine type
                //TODO : use common subtype
                if (indexType == Type.ANY_TYPE) {
                    indexType = nodeIndexType;
                } else {
                    //Broaden type
                    //TODO : use common supertype
                    if (indexType != nodeIndexType)
                        {indexType = Type.ITEM;}
                }
            }
        }
        return indexType;
    }

    @Override
    public void clearContext(int contextId) throws XPathException {
        NodeProxy p;
        for (final Iterator<NodeProxy> i = iterator(); i.hasNext(); ) {
            p = i.next();
            p.clearContext(contextId);
        }
    }

    @Override
    public void nodeMoved(NodeId oldNodeId, StoredNode newNode) {
        final NodeProxy p = get((DocumentImpl)newNode.getOwnerDocument(), oldNodeId);
        if (p != null)
            {p.nodeMoved(oldNodeId, newNode);}
    }

    /* (non-Javadoc)
     * @see org.exist.xquery.value.AbstractSequence#isPersistentSet()
     */
    @Override
    public boolean isPersistentSet() {
        // node sets are always persistent
        return true;
    }

    @Override
    public String toString() {
        final StringBuilder result = new StringBuilder();
        result.append("NodeSet(");
        for (int i = 0 ; i < getLength() ; i++) {
            if(i > 0)
                {result.append(", ");}
            final NodeProxy p = get(i);
            result.append("[").append(p.getDocument().getDocId()).append(":").append(p.getNodeId()).append("]");
        }
        result.append(")");
        return result.toString();
    }

    private class CollectionIterator implements Iterator<Collection> {

        Collection nextCollection = null;
        NodeSetIterator nodeIterator = iterator();

        CollectionIterator() {
            if (nodeIterator.hasNext()) {
                final NodeProxy p = nodeIterator.next();
                nextCollection = p.getDocument().getCollection();
            }
        }

        public boolean hasNext() {
            return nextCollection != null;
        }

        public Collection next() {
            final Collection oldCollection = nextCollection;
            nextCollection = null;
            while (nodeIterator.hasNext()) {
                final NodeProxy p = nodeIterator.next();
                if (!p.getDocument().getCollection().equals(oldCollection)) {
                    nextCollection = p.getDocument().getCollection();
                    break;
                }
            }
            return oldCollection;
        }

        public void remove() {
            // not needed
            throw new IllegalStateException();
        }
    }
}
TOP

Related Classes of org.exist.dom.AbstractNodeSet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.