From adaaa1f26d61a7a2526b6b7ba2c48100c0b64fc1 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Thu, 3 Oct 2024 22:16:51 +0200 Subject: [PATCH] WIP --- .../exist/dom/persistent/AVLTreeNodeSet.java | 35 +- .../dom/persistent/AbstractArrayNodeSet.java | 2 +- .../persistent/BackwardNodeRangeIterator.java | 84 +++ .../exist/dom/persistent/EmptyNodeSet.java | 40 +- .../exist/dom/persistent/ExtArrayNodeSet.java | 413 +++++++++++-- .../persistent/ForwardNodeRangeIterator.java | 84 +++ .../exist/dom/persistent/NewArrayNodeSet.java | 506 ++++++++++++++-- .../org/exist/dom/persistent/NodeProxy.java | 57 +- .../dom/persistent/NodeRangeIterator.java | 51 ++ .../org/exist/dom/persistent/NodeSet.java | 26 +- .../exist/dom/persistent/NodeSetHelper.java | 3 + .../exist/dom/persistent/SortedNodeSet.java | 36 +- .../exist/dom/persistent/VirtualNodeSet.java | 60 +- .../xquery/FollowingSiblingSelector.java | 77 ++- .../java/org/exist/xquery/LocationStep.java | 5 +- .../xquery/PrecedingSiblingSelector.java | 105 +++- .../main/java/org/exist/xquery/Profiler.java | 2 +- .../dom/persistent/NewArrayNodeSetTest.java | 543 +++++++++++++++++- .../java/org/exist/xquery/XPathQueryTest.java | 10 +- 19 files changed, 2007 insertions(+), 132 deletions(-) create mode 100644 exist-core/src/main/java/org/exist/dom/persistent/BackwardNodeRangeIterator.java create mode 100644 exist-core/src/main/java/org/exist/dom/persistent/ForwardNodeRangeIterator.java create mode 100644 exist-core/src/main/java/org/exist/dom/persistent/NodeRangeIterator.java diff --git a/exist-core/src/main/java/org/exist/dom/persistent/AVLTreeNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/AVLTreeNodeSet.java index 8a048ea1804..dcb39615a6b 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/AVLTreeNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/AVLTreeNodeSet.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import java.util.ArrayDeque; +import java.util.Collections; import java.util.Deque; import java.util.Iterator; @@ -459,12 +460,42 @@ private void setHasChanged() { } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); } diff --git a/exist-core/src/main/java/org/exist/dom/persistent/AbstractArrayNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/AbstractArrayNodeSet.java index 634edb1c706..13c4b859a55 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/AbstractArrayNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/AbstractArrayNodeSet.java @@ -92,7 +92,7 @@ public void add(final NodeProxy proxy, final int sizeHint) { addInternal(proxy, sizeHint); - this.isSorted = false; + this.isSorted = false; // TODO(AR) make this more intelligent by comparing the entry we are adding with the last entry added; if they are added in order we don't need to sort them! Then also apply that approach to other NodeSet implementations setHasChanged(); checkItemType(proxy.getType()); this.lastAdded = proxy; diff --git a/exist-core/src/main/java/org/exist/dom/persistent/BackwardNodeRangeIterator.java b/exist-core/src/main/java/org/exist/dom/persistent/BackwardNodeRangeIterator.java new file mode 100644 index 00000000000..cc46fa3d82e --- /dev/null +++ b/exist-core/src/main/java/org/exist/dom/persistent/BackwardNodeRangeIterator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * This library 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; version 2.1. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.dom.persistent; + +import javax.annotation.Nullable; +import java.util.NoSuchElementException; + +/** + * Iterate backward over a range of nodes. + * + * @author Adam Retter + */ +public class BackwardNodeRangeIterator implements NodeRangeIterator { + @Nullable private NodeProxy[] nodes; + private int idx; + private int startIdx; + + public BackwardNodeRangeIterator() { + this.nodes = null; + this.idx = -1; + this.startIdx = 0; + } + + /** + * @param nodes the nodes array. + * @param startIdx the starting index within {@link #nodes}. + * @param endIdx the ending index within {@link #nodes}. + */ + public BackwardNodeRangeIterator(final NodeProxy[] nodes, final int startIdx, final int endIdx) { + this.nodes = nodes; + this.startIdx = startIdx; + this.idx = endIdx; + } + + @Override + public boolean hasNext() { + return idx >= startIdx; + } + + @Override + public NodeProxy next() { + if (!hasNext() || nodes == null) { + throw new NoSuchElementException(); + } + return nodes[idx--]; + } + + @Override + public void reset(final NodeProxy[] nodes, final int startIdx, final int endIdx) { + this.nodes = nodes; + this.startIdx = startIdx; + this.idx = endIdx; + } +} diff --git a/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java index 4cbbfd77e84..7e07ff680c6 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/EmptyNodeSet.java @@ -27,6 +27,8 @@ import org.exist.xquery.value.SequenceIterator; import org.w3c.dom.Node; +import javax.annotation.Nullable; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; @@ -109,13 +111,43 @@ public NodeProxy parentWithChild(final DocumentImpl doc, final NodeId nodeId, } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { - return false; + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + return null; } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { - return false; + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + return null; + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + return Collections.emptyIterator(); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + return Collections.emptyIterator(); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + return Collections.emptyIterator(); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + return Collections.emptyIterator(); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + return Collections.emptyIterator(); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + return Collections.emptyIterator(); } @Override diff --git a/exist-core/src/main/java/org/exist/dom/persistent/ExtArrayNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/ExtArrayNodeSet.java index 1175362c4e9..e61bff4df8c 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/ExtArrayNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/ExtArrayNodeSet.java @@ -44,6 +44,7 @@ import javax.annotation.Nullable; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; @@ -333,17 +334,59 @@ public NodeProxy parentWithChild(final DocumentImpl doc, final NodeId nodeId, } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { sort(); lastPart = getPart(doc, false, initialSize); - return lastPart == null ? null : lastPart.containsPrecedingSiblingOf(doc, nodeId); + return lastPart == null ? null : lastPart.containsPrecedingSiblingOf(nodeId); } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { sort(); lastPart = getPart(doc, false, initialSize); - return lastPart == null ? null : lastPart.containsFollowingSiblingOf(doc, nodeId); + return lastPart == null ? null : lastPart.containsFollowingSiblingOf(nodeId); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.precedingSiblingsOf(nodeId); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.precedingSiblingsOfReverse(nodeId); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.precedingSiblingsOf(nodeId, it); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.followingSiblingsOf(nodeId); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.followingSiblingsOfReverse(nodeId); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + sort(); + lastPart = getPart(doc, false, initialSize); + return lastPart == null ? null : lastPart.followingSiblingsOf(nodeId, it); } /** @@ -822,66 +865,346 @@ NodeProxy parentWithChild(final DocumentImpl doc, final NodeId nodeId, final boo } /** - * Tests this nodeset contains a preceding sibling of the provided node (e.g. a following sibling). + * Implements a right-most binary search for an immediate preceding sibling. * - * @param doc the document containing the node to test. * @param nodeId the nodeId of the node to test. * - * @return true if the node is a following sibling of a node in this nodeset. + * @return the immediate preceding sibling node, or null if there is no such node in this set. */ - boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + @Nullable NodeProxy containsPrecedingSiblingOf(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#containsPrecedingSiblingOf(int NodeId), a bug in one might indicate a bug in the other sort(); - int low = 0; - int high = length - 1; - int mid; - int cmp; - NodeProxy p; - while (low <= high) { - mid = (low + high) / 2; - p = array[mid]; - cmp = p.getNodeId().compareTo(nodeId); - if (cmp < 0) { - if (nodeId.isPrecedingSiblingOf(p.getNodeId())) { - return true; - } - low = mid + 1; + + int l = 0; + int r = length; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = array[m].getNodeId().compareTo(nodeId); + if (comparison >= 0) { + r = m; } else { - high = mid - 1; + l = m + 1; } } - return false; + + if (r > 0) { + final NodeProxy nodeR = array[r - 1]; + if (nodeId.isFollowingSiblingOf(nodeR.getNodeId())) { + return nodeR; + } + } + + return null; } /** - * Tests this nodeset contains a following sibling of the provided node (e.g. a preceding sibling). + * Implements a left-most binary search for an immediate following sibling. * - * @param doc the document containing the node to test. * @param nodeId the nodeId of the node to test. * - * @return true if the node is a preceding sibling of a node in this nodeset. + * @return the immediate following sibling node, or null if there is no such node in this set. */ - boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { - // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#hasPrecedingSibling, a bug in one might indicate a bug in the other + @Nullable NodeProxy containsFollowingSiblingOf(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#containsFollowingSiblingOf(int, NodeId), a bug in one might indicate a bug in the other sort(); - int low = 0; - int high = length - 1; - int mid; - int cmp; - NodeProxy p; - while (low <= high) { - mid = (low + high) / 2; - p = array[mid]; - cmp = p.getNodeId().compareTo(nodeId); - if (cmp > 0) { - if (nodeId.isPrecedingSiblingOf(p.getNodeId())) { - return true; - } - high = mid - 1; + + int l = 0; + int r = length; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = array[m].getNodeId().compareTo(nodeId); + if (comparison <= 0) { + l = m + 1; } else { - low = mid + 1; + r = m; } } - return false; + + if (l < length) { + final NodeProxy nodeL = array[l]; + if (nodeId.isPrecedingSiblingOf(nodeL.getNodeId())) { + return nodeL; + } + } + + return null; + } + + public Iterator precedingSiblingsOf(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#precedingSiblingsOf(DocumentImpl, NodeId), a bug in one might indicate a bug in the other + return precedingSiblingsOf(nodeId, new BackwardNodeRangeIterator()); + } + + public Iterator precedingSiblingsOfReverse(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#precedingSiblingsOfReverse(DocumentImpl, NodeId), a bug in one might indicate a bug in the other + return precedingSiblingsOf(nodeId, new ForwardNodeRangeIterator()); + } + + public Iterator precedingSiblingsOf(final NodeId nodeId, final NodeRangeIterator it) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#precedingSiblingsOf(int, NodeId, NodeRangeIterator), a bug in one might indicate a bug in the other + sort(); + + int l = 0; + int n = length; + + l = lastPrecedingSiblingOf(l, n, nodeId); + if (l == -1) { + return Collections.emptyIterator(); + } + + n = n - l; + final int r = firstPrecedingSiblingOf(l, n, nodeId); + + it.reset(array, l, r); + return it; + } + + /** + * Finds the last preceding sibling, e.g. preceding-sibling::node()[last()]. + * For example the following returns {@code } as the last preceding sibling of {@code }: + *
{@code
+         * document {
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         * }/letters/d/preceding-sibling::element()[last()]
+         * }
+ * + * Implements a left-most binary search for a preceding sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last preceding sibling node, or -1 if there is no such node in this set. + */ + private int lastPrecedingSiblingOf(int l, final int n, final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#lastPrecedingSiblingOf(int, int, NodeId), a bug in one might indicate a bug in the other + int r = l + n; + + final NodeId parentId = nodeId.getParentId(); + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + comparison = array[m].getNodeId().compareTo(nodeId); + if (!array[m].getNodeId().getParentId().equals(parentId) || comparison > 0) { + // NOT matched! + l = m + 1; + } else { + // Maybe matched... + r = m; + } + } + + if (l < n) { + final NodeProxy nodeL = array[l]; + if (nodeId.isFollowingSiblingOf(nodeL.getNodeId())) { + return l; + } + } + + return -1; + } + + /** + * Finds the first preceding sibling, e.g. preceding-sibling::node()[1]. + * For example the following returns {@code } as the first preceding sibling of {@code }: + *
{@code
+         * document {
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         * }/letters/d/preceding-sibling::element()[1]
+         * }
+ * + * Implements a right-most binary search for a preceding sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first preceding sibling, or -1 if there is no such node in this set. + */ + private int firstPrecedingSiblingOf(int l, final int n, final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#firstPrecedingSiblingOf(int, int, NodeId), a bug in one might indicate a bug in the other + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = array[m].getNodeId().compareTo(nodeId); + if (comparison >= 0) { + r = m; + } else { + l = m + 1; + } + } + + + if (r > 0) { + final NodeProxy nodeR = array[r - 1]; + if (nodeId.isFollowingSiblingOf(nodeR.getNodeId())) { + return r - 1; + } + } + + return -1; + } + + public Iterator followingSiblingsOf(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#followingSiblingsOf(DocumentImpl, NodeId), a bug in one might indicate a bug in the other + return followingSiblingsOf(nodeId, new ForwardNodeRangeIterator()); + } + + public Iterator followingSiblingsOfReverse(final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#followingSiblingsOfReverse(DocumentImpl, NodeId), a bug in one might indicate a bug in the other + return followingSiblingsOf(nodeId, new BackwardNodeRangeIterator()); + } + + public Iterator followingSiblingsOf(final NodeId nodeId, final NodeRangeIterator it) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#followingSiblingsOf(int, NodeId, NodeRangeIterator), a bug in one might indicate a bug in the other + sort(); + + int l = 0; + int n = length; + + l = firstFollowingSiblingOf(l, n, nodeId); + if (l == -1) { + return Collections.emptyIterator(); + } + + n = n - l; + final int r = lastFollowingSiblingOf(l, n, nodeId); + + it.reset(array, l, r); + return it; + } + + /** + * Finds the first following sibling, e.g. following-sibling::node()[1]. + * For example the following returns {@code } as the first following sibling of {@code }: + *
{@code
+         * document {
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         * }/letters/b/following-sibling::element()[1]
+         * }
+ * + * Implements a left-most binary search for a following sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first following sibling, or -1 if there is no such node in this set. + */ + private int firstFollowingSiblingOf(int l, final int n, final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#firstFollowingSiblingOf(int, int, NodeId), a bug in one might indicate a bug in the other + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = array[m].getNodeId().compareTo(nodeId); + if (comparison <= 0) { + l = m + 1; + } else { + r = m; + } + } + + if (l < n) { + final NodeProxy nodeL = array[l]; + if (nodeId.isPrecedingSiblingOf(nodeL.getNodeId())) { + return l; + } + } + + return -1; + } + + /** + * Finds the last following sibling, e.g. following-sibling::node()[last()]. + * For example the following returns {@code } as the last preceding sibling of {@code }: + *
{@code
+         * document {
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         *      
+         * }/letters/b/following-sibling::element()[last()]
+         * }
+ * + * Implements a right-most binary search for a following sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last following sibling node, or -1 if there is no such node in this set. + */ + private int lastFollowingSiblingOf(int l, final int n, final NodeId nodeId) { + // NOTE(AR) this is almost a copy of the code in NewArrayNodeSet#lastFollowingSiblingOf(int, int, NodeId), a bug in one might indicate a bug in the other + int r = l + n; + + final NodeId parentId = nodeId.getParentId(); + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + comparison = array[m].getNodeId().compareTo(nodeId); + if (!array[m].getNodeId().getParentId().equals(parentId) || comparison < 0) { + // NOT matched! + r = m; + } else { + // Maybe matched... + l = m + 1; + } + } + + if (r > 0) { + final NodeProxy nodeR = array[r - 1]; + if (nodeId.isPrecedingSiblingOf(nodeR.getNodeId())) { + return r - 1; + } + } + + return -1; } NodeProxy hasDescendantsInSet(final NodeId ancestorId, final int contextId, final boolean includeSelf) { diff --git a/exist-core/src/main/java/org/exist/dom/persistent/ForwardNodeRangeIterator.java b/exist-core/src/main/java/org/exist/dom/persistent/ForwardNodeRangeIterator.java new file mode 100644 index 00000000000..49054a5c2a9 --- /dev/null +++ b/exist-core/src/main/java/org/exist/dom/persistent/ForwardNodeRangeIterator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * This library 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; version 2.1. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.dom.persistent; + +import javax.annotation.Nullable; +import java.util.NoSuchElementException; + +/** + * Iterate forward over a range of nodes. + * + * @author Adam Retter + */ +public class ForwardNodeRangeIterator implements NodeRangeIterator { + @Nullable private NodeProxy[] nodes; + private int idx; + private int endIdx; + + public ForwardNodeRangeIterator() { + this.nodes = null; + this.idx = 0; + this.endIdx = -1; + } + + /** + * @param nodes the nodes array. + * @param startIdx the starting index within the nodes array. + * @param endIdx the ending index within the nodes array. + */ + public ForwardNodeRangeIterator(final NodeProxy[] nodes, final int startIdx, final int endIdx) { + this.nodes = nodes; + this.idx = startIdx; + this.endIdx = endIdx; + } + + @Override + public boolean hasNext() { + return idx <= endIdx; + } + + @Override + public NodeProxy next() { + if (!hasNext() || nodes == null) { + throw new NoSuchElementException(); + } + return nodes[idx++]; + } + + @Override + public void reset(final NodeProxy[] nodes, final int startIdx, final int endIdx) { + this.nodes = nodes; + this.idx = startIdx; + this.endIdx = endIdx; + } +} diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java index ec02532fa17..c19cc2ae63e 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/NewArrayNodeSet.java @@ -26,6 +26,7 @@ import org.exist.dom.INode; import org.exist.numbering.NodeId; import org.exist.storage.DBBroker; +import org.exist.storage.dom.NodeIterator; import org.exist.storage.lock.LockManager; import org.exist.storage.lock.ManagedDocumentLock; import org.exist.util.FastQSort; @@ -78,7 +79,7 @@ public class NewArrayNodeSet extends AbstractArrayNodeSet implements ExtNodeSet, /** * An array of offsets into {@link #nodes}, - * the index is the index from {@link #documentNodesOffset}. + * the index is the index from {@link #documentIds}. */ private int documentNodesOffset[] = new int[16]; @@ -139,7 +140,7 @@ private void ensureCapacity() { } } - private int findDoc(DocumentImpl doc) { + int findDoc(final DocumentImpl doc) { return findDoc(doc.getDocId()); } @@ -914,71 +915,492 @@ private NodeProxy parentWithChild(final int docIdx, final NodeId nodeId, final b } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { sort(); final int docIdx = findDoc(doc); if (docIdx < 0) { - return false; + return null; } return containsPrecedingSiblingOf(docIdx, nodeId); } - private boolean containsPrecedingSiblingOf(final int docIdx, final NodeId nodeId) { + /** + * Implements a right-most binary search for an immediate preceding sibling. + * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for an immediate preceding sibling of. + * + * @return the immediate preceding sibling node, or null if there is no such node in this set. + */ + private @Nullable NodeProxy containsPrecedingSiblingOf(final int docIdx, final NodeId nodeId) { + // NOTE(AR) this is a copy of the code in {@link #firstPrecedingSiblingOf(int, int, NodeId)} with a small adaption to directly return {@code nodeR} + final int n = documentNodesCount[docIdx]; + int l = documentNodesOffset[docIdx]; + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + if (comparison >= 0) { + r = m; + } else { + l = m + 1; + } + } + + if (r > 0) { + final NodeProxy nodeR = nodes[r - 1]; + if (nodeId.isFollowingSiblingOf(nodeR.getNodeId())) { + return nodeR; + } + } + + return null; + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + return precedingSiblingsOf(doc, nodeId, new BackwardNodeRangeIterator()); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + return precedingSiblingsOf(doc, nodeId, new ForwardNodeRangeIterator()); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { sort(); - // NOTE(AR) this is almost a copy of the code in ExtArrayNodeSet#containsPrecedingSiblingOf, a bug in one might indicate a bug in the other - int low = documentNodesOffset[docIdx]; - int high = low + (documentNodesCount[docIdx] - 1); - int mid; - int cmp; - NodeProxy p; - while (low <= high) { - mid = (low + high) / 2; - p = nodes[mid]; - cmp = p.getNodeId().compareTo(nodeId); - if (cmp < 0) { - if (nodeId.isPrecedingSiblingOf(p.getNodeId())) { - return true; - } - low = mid + 1; + final int docIdx = findDoc(doc); + if (docIdx < 0) { + return Collections.emptyIterator(); + } + return precedingSiblingsOf(docIdx, nodeId, it); + } + + private Iterator precedingSiblingsOf(final int docIdx, final NodeId nodeId, final NodeRangeIterator it) { + int l = documentNodesOffset[docIdx]; + int n = documentNodesCount[docIdx]; + + l = lastPrecedingSiblingOf(l, n, nodeId); + if (l == -1) { + return Collections.emptyIterator(); + } + + n = n - l; + final int r = firstPrecedingSiblingOf(l, n, nodeId); + + it.reset(nodes, l, r); + return it; + } + + /** + * Finds the last preceding sibling, e.g. preceding-sibling::node()[last()]. + * For example the following returns {@code } as the last preceding sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/d/preceding-sibling::element()[last()]
+     * }
+ * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last preceding sibling node, or -1 if there is no such node in this set. + */ + private int lastPrecedingSiblingOf(final int docIdx, final NodeId nodeId) { + final int n = documentNodesCount[docIdx]; + final int l = documentNodesOffset[docIdx]; + return lastPrecedingSiblingOf(l, n, nodeId); + } + + /** + * Finds the last preceding sibling, e.g. preceding-sibling::node()[last()]. + * For example the following returns {@code } as the last preceding sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/d/preceding-sibling::element()[last()]
+     * }
+ * + * Implements a left-most binary search for a preceding sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last preceding sibling node, or -1 if there is no such node in this set. + */ + private int lastPrecedingSiblingOf(int l, final int n, final NodeId nodeId) { + int r = l + n; + + final NodeId parentId = nodeId.getParentId(); + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + +// System.out.println(comparison + " = " + nodes[m].getNodeId() + " compareTo " + nodeId); + + if (!nodes[m].getNodeId().getParentId().equals(parentId) || comparison > 0) { + // NOT matched! + l = m + 1; +// System.out.println("NOPE!"); } else { - high = mid - 1; + // Maybe matched... +// System.out.println("Maybe..."); + r = m; } } - return false; + + if (l < n) { + final NodeProxy nodeL = nodes[l]; + if (nodeId.isFollowingSiblingOf(nodeL.getNodeId())) { + return l; + } + } + + return -1; + } + + /** + * Finds the first preceding sibling, e.g. preceding-sibling::node()[1]. + * For example the following returns {@code } as the first preceding sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/d/preceding-sibling::element()[1]
+     * }
+ * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first preceding sibling, or -1 if there is no such node in this set. + */ + private int firstPrecedingSiblingOf(final int docIdx, final NodeId nodeId) { + final int n = documentNodesCount[docIdx]; + final int l = documentNodesOffset[docIdx]; + return firstPrecedingSiblingOf(l, n, nodeId); + } + + /** + * Finds the first preceding sibling, e.g. preceding-sibling::node()[1]. + * For example the following returns {@code } as the first preceding sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/d/preceding-sibling::element()[1]
+     * }
+ * + * Implements a right-most binary search for a preceding sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first preceding sibling, or -1 if there is no such node in this set. + */ + private int firstPrecedingSiblingOf(int l, final int n, final NodeId nodeId) { + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + if (comparison >= 0) { + r = m; + } else { + l = m + 1; + } + } + + + if (r > 0) { + final NodeProxy nodeR = nodes[r - 1]; + if (nodeId.isFollowingSiblingOf(nodeR.getNodeId())) { + return r - 1; + } + } + + return -1; } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { sort(); final int docIdx = findDoc(doc); if (docIdx < 0) { - return false; + return null; } return containsFollowingSiblingOf(docIdx, nodeId); } - private boolean containsFollowingSiblingOf(final int docIdx, final NodeId nodeId) { + /** + * Implements a left-most binary search for an immediate following sibling. + * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for an immediate following sibling of. + * + * @return the immediate following sibling node, or null if there is no such node in this set. + */ + private @Nullable NodeProxy containsFollowingSiblingOf(final int docIdx, final NodeId nodeId) { + // NOTE(AR) this is a copy of the code in {@link #firstFollowingSiblingOf(int, int, NodeId)} with a small adaption to directly return {@code nodeL} + final int n = documentNodesCount[docIdx]; + int l = documentNodesOffset[docIdx]; + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + if (comparison <= 0) { + l = m + 1; + } else { + r = m; + } + } + + if (l < n) { + final NodeProxy nodeL = nodes[l]; + if (nodeId.isPrecedingSiblingOf(nodeL.getNodeId())) { + return nodeL; + } + } + + return null; + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + return followingSiblingsOf(doc, nodeId, new ForwardNodeRangeIterator()); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + return followingSiblingsOf(doc, nodeId, new BackwardNodeRangeIterator()); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { sort(); - // NOTE(AR) this is almost a copy of the code in ExtArrayNodeSet.Part#containsFollowingSiblingOf, a bug in one might indicate a bug in the other - int low = documentNodesOffset[docIdx]; - int high = low + (documentNodesCount[docIdx] - 1); - int mid; - int cmp; - NodeProxy p; - while (low <= high) { - mid = (low + high) / 2; - p = nodes[mid]; - cmp = p.getNodeId().compareTo(nodeId); - if (cmp > 0) { - if (nodeId.isPrecedingSiblingOf(p.getNodeId())) { - return true; - } - high = mid - 1; + final int docIdx = findDoc(doc); + if (docIdx < 0) { + return Collections.emptyIterator(); + } + return followingSiblingsOf(docIdx, nodeId, it); + } + + private Iterator followingSiblingsOf(final int docIdx, final NodeId nodeId, final NodeRangeIterator it) { + int l = documentNodesOffset[docIdx]; + int n = documentNodesCount[docIdx]; + + l = firstFollowingSiblingOf(l, n, nodeId); + if (l == -1) { + return Collections.emptyIterator(); + } + + n = n - l; + final int r = lastFollowingSiblingOf(l, n, nodeId); + + it.reset(nodes, l, r); + return it; + } + + /** + * Finds the first following sibling, e.g. following-sibling::node()[1]. + * For example the following returns {@code } as the first following sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/b/following-sibling::element()[1]
+     * }
+ * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first following sibling, or -1 if there is no such node in this set. + */ + private int firstFollowingSiblingOf(final int docIdx, final NodeId nodeId) { + final int n = documentNodesCount[docIdx]; + final int l = documentNodesOffset[docIdx]; + return firstFollowingSiblingOf(l, n, nodeId); + } + + /** + * Finds the first following sibling, e.g. following-sibling::node()[1]. + * For example the following returns {@code } as the first following sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/b/following-sibling::element()[1]
+     * }
+ * + * Implements a left-most binary search for a following sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the first following sibling, or -1 if there is no such node in this set. + */ + private int firstFollowingSiblingOf(int l, final int n, final NodeId nodeId) { + int r = l + n; + + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + if (comparison <= 0) { + l = m + 1; } else { - low = mid + 1; + r = m; } } - return false; + + if (l < n) { + final NodeProxy nodeL = nodes[l]; + if (nodeId.isPrecedingSiblingOf(nodeL.getNodeId())) { + return l; + } + } + + return -1; + } + + /** + * Finds the last following sibling, e.g. following-sibling::node()[last()]. + * For example the following returns {@code } as the last following sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/b/following-sibling::element()[last()]
+     * }
+ * + * @param docIdx the index of the document in the NewArrayNodeSet. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last following sibling node, or -1 if there is no such node in this set. + */ + private int lastFollowingSiblingOf(final int docIdx, final NodeId nodeId) { + final int n = documentNodesCount[docIdx]; + final int l = documentNodesOffset[docIdx]; + return lastFollowingSiblingOf(l, n, nodeId); + } + + /** + * Finds the last following sibling, e.g. following-sibling::node()[last()]. + * For example the following returns {@code } as the last preceding sibling of {@code }: + *
{@code
+     * document {
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     *      
+     * }/letters/b/following-sibling::element()[last()]
+     * }
+ * + * Implements a right-most binary search for a following sibling. + * + * @param l the left-most starting index within {@code documentNodesOffset}. + * @param n - the number of the nodes to search from {@code l}. + * @param nodeId the node to search for a preceding sibling of. + * + * @return the index of the last following sibling node, or -1 if there is no such node in this set. + */ + private int lastFollowingSiblingOf(int l, final int n, final NodeId nodeId) { + int r = l + n; + + final NodeId parentId = nodeId.getParentId(); + int m; + int comparison; + + while (l < r) { + m = (l + r) / 2; + + comparison = nodes[m].getNodeId().compareTo(nodeId); + +// System.out.println(comparison + " = " + nodes[m].getNodeId() + " compareTo " + nodeId); + + if (!nodes[m].getNodeId().getParentId().equals(parentId) || comparison < 0) { + // NOT matched! + r = m; +// System.out.println("NOPE!"); + } else { + // Maybe matched... +// System.out.println("Maybe..."); + l = m + 1; + } + } + + if (r > 0) { + final NodeProxy nodeR = nodes[r - 1]; + if (nodeId.isPrecedingSiblingOf(nodeR.getNodeId())) { + return r - 1; + } + } + + return -1; } @Override diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java b/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java index e57011ac2c1..4e2d795aabb 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.java @@ -49,6 +49,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.IOException; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Properties; @@ -540,6 +541,7 @@ public void addContextNode(final int contextId, final NodeValue node) { final NodeProxy contextNode = (NodeProxy) node; if(context == null) { context = new ContextItem(contextId, contextNode); +// System.out.println("\t\t\t***SET newContextItem= " + context.getNode().getNodeId()); return; } ContextItem next = context; @@ -556,6 +558,9 @@ public void addContextNode(final int contextId, final NodeValue node) { // context item, it will be shared. we thus have to create // a copy before appending a new item. next = new ContextItem(next.getContextId(), next.getNode()); + +// System.out.println("\t\t\t*** SET context= " + context.getNode().getNodeId()); + context = next; } next.setNextContextItem(new ContextItem(contextId, contextNode)); @@ -1009,19 +1014,55 @@ public NodeProxy parentWithChild(final DocumentImpl otherDoc, final NodeId other } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl otherDoc, final NodeId otherId) { - if (otherDoc.getDocId() != doc.getDocId()) { - return false; + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl otherDoc, final NodeId otherId) { + if (otherDoc.getDocId() == doc.getDocId() && otherId.isFollowingSiblingOf(nodeId)) { + return this; } - return otherId.isFollowingSiblingOf(nodeId); + return null; } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl otherDoc, final NodeId otherId) { - if (otherDoc.getDocId() != doc.getDocId()) { - return false; + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl otherDoc, final NodeId otherId) { + if (otherDoc.getDocId() == doc.getDocId() && otherId.isPrecedingSiblingOf(nodeId)) { + return this; + } + return null; + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl otherDoc, final NodeId otherId) { + return precedingSiblingsOf(otherDoc, otherId, null); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl otherDoc, final NodeId otherId) { + return precedingSiblingsOf(otherDoc, otherId, null); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl otherDoc, final NodeId otherId, final NodeRangeIterator it) { + if (otherDoc.getDocId() == doc.getDocId() && otherId.isFollowingSiblingOf(nodeId)) { + return Collections.singletonList(this).iterator(); + } + return Collections.emptyIterator(); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl otherDoc, final NodeId otherId) { + return followingSiblingsOf(otherDoc, otherId, null); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl otherDoc, final NodeId otherId) { + return followingSiblingsOf(otherDoc, otherId, null); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl otherDoc, final NodeId otherId, final NodeRangeIterator it) { + if (otherDoc.getDocId() == doc.getDocId() && otherId.isPrecedingSiblingOf(nodeId)) { + return Collections.singletonList(this).iterator(); } - return otherId.isPrecedingSiblingOf(nodeId); + return Collections.emptyIterator(); } @Override diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NodeRangeIterator.java b/exist-core/src/main/java/org/exist/dom/persistent/NodeRangeIterator.java new file mode 100644 index 00000000000..9b548c7bc4b --- /dev/null +++ b/exist-core/src/main/java/org/exist/dom/persistent/NodeRangeIterator.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014, Evolved Binary Ltd + * + * This file was originally ported from FusionDB to eXist-db by + * Evolved Binary, for the benefit of the eXist-db Open Source community. + * Only the ported code as it appears in this file, at the time that + * it was contributed to eXist-db, was re-licensed under The GNU + * Lesser General Public License v2.1 only for use in eXist-db. + * + * This license grant applies only to a snapshot of the code as it + * appeared when ported, it does not offer or infer any rights to either + * updates of this source code or access to the original source code. + * + * The GNU Lesser General Public License v2.1 only license follows. + * + * --------------------------------------------------------------------- + * + * Copyright (C) 2014, Evolved Binary Ltd + * + * This library 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; version 2.1. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.dom.persistent; + +import java.util.Iterator; + +/** + * Iterate over a range of nodes. + * + * @author Adam Retter + */ +public interface NodeRangeIterator extends Iterator { + /** + * Reset the position of the iterator. + * + * @param nodes the nodes array. + * @param startIdx the starting index within the nodes array. + * @param endIdx the ending index within the nodes array. + */ + void reset(final NodeProxy[] nodes, final int startIdx, final int endIdx); +} diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/NodeSet.java index 1cd17211134..15f9f09b4c7 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/NodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/NodeSet.java @@ -29,6 +29,7 @@ import org.exist.xquery.value.Type; import org.w3c.dom.NodeList; +import javax.annotation.Nullable; import java.util.Iterator; /** @@ -387,22 +388,35 @@ boolean matchAncestorDescendant(NodeSet al, int mode, boolean includeSelf, void setTrackMatches(boolean track); /** - * Tests this nodeset contains a preceding sibling of the provided node (e.g. a following sibling). + * Searches for an immediate preceding sibling of the provided node (a following sibling). * * @param doc the document containing the node to test. * @param nodeId the nodeId of the node to test. * - * @return true if the node is a following sibling of a node in this nodeset. + * @return the immediate preceding-sibling node if the node is a following sibling of a node in this set, else null. */ - boolean containsPrecedingSiblingOf(DocumentImpl doc, NodeId nodeId); + @Nullable NodeProxy containsPrecedingSiblingOf(DocumentImpl doc, NodeId nodeId); /** - * Tests this nodeset contains a following sibling of the provided node (e.g. a preceding sibling). + * Searches for an immediate following sibling of the provided node (a preceding sibling). * * @param doc the document containing the node to test. * @param nodeId the nodeId of the node to test. * - * @return true if the node is a preceding sibling of a node in this nodeset. + * @return the immediate following-sibling node if the node is a preceding sibling of a node in this set, else null. */ - boolean containsFollowingSiblingOf(DocumentImpl doc, NodeId nodeId); + @Nullable NodeProxy containsFollowingSiblingOf(DocumentImpl doc, NodeId nodeId); + + + Iterator precedingSiblingsOf(DocumentImpl doc, NodeId nodeId); + + Iterator precedingSiblingsOfReverse(DocumentImpl doc, NodeId nodeId); + + Iterator precedingSiblingsOf(DocumentImpl doc, NodeId nodeId, NodeRangeIterator it); + + Iterator followingSiblingsOf(DocumentImpl doc, NodeId nodeId); + + Iterator followingSiblingsOfReverse(DocumentImpl doc, NodeId nodeId); + + Iterator followingSiblingsOf(DocumentImpl doc, NodeId nodeId, NodeRangeIterator it); } diff --git a/exist-core/src/main/java/org/exist/dom/persistent/NodeSetHelper.java b/exist-core/src/main/java/org/exist/dom/persistent/NodeSetHelper.java index 4d1a6caf3c7..4542ae6b7c3 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/NodeSetHelper.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/NodeSetHelper.java @@ -506,6 +506,9 @@ public static NodeSet selectPrecedingSiblings(final NodeSet candidates, */ public static NodeSet selectFollowingSiblings(final NodeSet candidates, final NodeSet references, final int contextId) { + + // TODO(AR) rewrite this used DLN.isPrecedingSiblingOf and DLN.isFollowingSiblingOf + if(references.isEmpty() || candidates.isEmpty()) { return NodeSet.EMPTY_SET; } diff --git a/exist-core/src/main/java/org/exist/dom/persistent/SortedNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/SortedNodeSet.java index 00b8a5417b8..b658a2e32b4 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/SortedNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/SortedNodeSet.java @@ -40,6 +40,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.annotation.Nullable; import java.io.StringReader; import java.util.Iterator; import java.util.NoSuchElementException; @@ -222,12 +223,43 @@ public SequenceIterator unorderedIterator() { } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { throw new UnsupportedOperationException("TODO(AR) do we need to implement this?"); } diff --git a/exist-core/src/main/java/org/exist/dom/persistent/VirtualNodeSet.java b/exist-core/src/main/java/org/exist/dom/persistent/VirtualNodeSet.java index 75e00e87422..13f53bbdc44 100644 --- a/exist-core/src/main/java/org/exist/dom/persistent/VirtualNodeSet.java +++ b/exist-core/src/main/java/org/exist/dom/persistent/VirtualNodeSet.java @@ -39,9 +39,11 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import javax.annotation.Nullable; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import java.io.IOException; +import java.util.Collections; import java.util.Iterator; /** @@ -299,7 +301,7 @@ public NodeProxy parentWithChild(final DocumentImpl doc, final NodeId nodeId, } @Override - public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { if (realSet != null && realSetIsComplete) { return realSet.containsPrecedingSiblingOf(doc, nodeId); } else { @@ -308,7 +310,7 @@ public boolean containsPrecedingSiblingOf(final DocumentImpl doc, final NodeId n } @Override - public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { + public @Nullable NodeProxy containsFollowingSiblingOf(final DocumentImpl doc, final NodeId nodeId) { if (realSet != null && realSetIsComplete) { return realSet.containsFollowingSiblingOf(doc, nodeId); } else { @@ -316,6 +318,60 @@ public boolean containsFollowingSiblingOf(final DocumentImpl doc, final NodeId n } } + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + if (realSet != null && realSetIsComplete) { + return realSet.precedingSiblingsOf(doc, nodeId); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + + @Override + public Iterator precedingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + if (realSet != null && realSetIsComplete) { + return realSet.precedingSiblingsOfReverse(doc, nodeId); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + + @Override + public Iterator precedingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + if (realSet != null && realSetIsComplete) { + return realSet.precedingSiblingsOf(doc, nodeId, it); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId) { + if (realSet != null && realSetIsComplete) { + return realSet.followingSiblingsOf(doc, nodeId); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + + @Override + public Iterator followingSiblingsOfReverse(final DocumentImpl doc, final NodeId nodeId) { + if (realSet != null && realSetIsComplete) { + return realSet.followingSiblingsOfReverse(doc, nodeId); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + + @Override + public Iterator followingSiblingsOf(final DocumentImpl doc, final NodeId nodeId, final NodeRangeIterator it) { + if (realSet != null && realSetIsComplete) { + return realSet.followingSiblingsOf(doc, nodeId, it); + } else { + throw new UnsupportedOperationException("TODO(AR) do we need to implement this"); + } + } + /** * Realize the node set by recursively scanning the * DOM. diff --git a/exist-core/src/main/java/org/exist/xquery/FollowingSiblingSelector.java b/exist-core/src/main/java/org/exist/xquery/FollowingSiblingSelector.java index 63bc74d0b5b..a8d906f788f 100644 --- a/exist-core/src/main/java/org/exist/xquery/FollowingSiblingSelector.java +++ b/exist-core/src/main/java/org/exist/xquery/FollowingSiblingSelector.java @@ -32,11 +32,14 @@ */ package org.exist.xquery; -import org.exist.dom.persistent.DocumentImpl; -import org.exist.dom.persistent.NodeProxy; -import org.exist.dom.persistent.NodeSet; +import org.exist.dom.persistent.*; +import org.exist.numbering.DLN; import org.exist.numbering.NodeId; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * A node selector for following siblings. * @@ -45,15 +48,75 @@ public class FollowingSiblingSelector implements NodeSelector { private final NodeSet contextSet; + private final int contextId; + private @Nullable final Expression expression; + +// private @Nullable NewArrayNodeSet precedingSiblings; +private @Nullable List precedingSiblings; - public FollowingSiblingSelector(final NodeSet contextSet) { + public FollowingSiblingSelector(final NodeSet contextSet, final int contextId, @Nullable final Expression expression) { this.contextSet = contextSet; + this.contextId = contextId; + this.expression = expression; } +// @Override +// public @Nullable NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { +// final @Nullable NodeProxy precedingSibling = contextSet.containsPrecedingSiblingOf(doc, nodeId); +// if (precedingSibling != null) { +// final NodeProxy followingSibling = new NodeProxy(expression, doc, nodeId); +// +// // START TEMP +//// followingSibling.addContextNode(contextId, new NodeProxy(expression, doc, new DLN("3.2"))); +// // END TEMP +// +// if (Expression.IGNORE_CONTEXT != contextId) { +// if (Expression.NO_CONTEXT_ID == contextId) { +// followingSibling.copyContext(precedingSibling); +// } else { +// followingSibling.addContextNode(contextId, precedingSibling); +// } +// } +// return followingSibling; +// } +// return null; +// } + @Override - public NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { - if (contextSet.containsPrecedingSiblingOf(doc, nodeId)) { - return new NodeProxy(doc, nodeId); + public @Nullable NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { + final @Nullable NodeProxy precedingSibling = contextSet.containsPrecedingSiblingOf(doc, nodeId); + if (precedingSibling != null) { + + if (precedingSiblings == null) { + // TODO(AR) do we need to sort this and remove duplicates, or is just a simple list enough? +// precedingSiblings = new NewArrayNodeSet(); + this.precedingSiblings = new ArrayList<>(); + } + precedingSiblings.add(precedingSibling); + + final NodeProxy followingSibling = new NodeProxy(expression, doc, nodeId); + +// System.out.println("currentId=" + followingSibling.getNodeId()); + + if (Expression.IGNORE_CONTEXT != contextId) { +// int i = 0; + for (final NodeProxy contextNode : precedingSiblings) { +// System.out.println("\t\tSTART INNER LOOP: " + i); + + if (Expression.NO_CONTEXT_ID == contextId) { +// System.out.println("\t\t\t" + followingSibling.getNodeId().toString() + " .copyContext(" + contextNode.getNodeId().toString() + ")"); + followingSibling.copyContext(contextNode); + } else { +// System.out.println("\t\t\t" + followingSibling.getNodeId().toString() + " .addContextNode(" + contextId + ", " + contextNode.getNodeId().toString() + ")"); + followingSibling.addContextNode(contextId, contextNode); + } + +// System.out.println("\t\tEND INNER LOOP: " + i); +// i++; + } + } + + return followingSibling; } return null; } diff --git a/exist-core/src/main/java/org/exist/xquery/LocationStep.java b/exist-core/src/main/java/org/exist/xquery/LocationStep.java index 9e568b9444c..d964f62674b 100644 --- a/exist-core/src/main/java/org/exist/xquery/LocationStep.java +++ b/exist-core/src/main/java/org/exist/xquery/LocationStep.java @@ -872,13 +872,14 @@ protected Sequence getSiblings(final XQueryContext context, final Sequence conte final NodeSelector nodeSelector; if (axis == Constants.PRECEDING_SIBLING_AXIS) { - nodeSelector = new PrecedingSiblingSelector(contextSet); + nodeSelector = new PrecedingSiblingSelector(contextSet, contextId, this); } else if (axis == Constants.FOLLOWING_SIBLING_AXIS) { - nodeSelector = new FollowingSiblingSelector(contextSet); + nodeSelector = new FollowingSiblingSelector(contextSet, contextId, this); } else { throw new IllegalArgumentException("Unsupported axis specified"); } + // TODO(AR) (2) if the outer expression is fn:exists or fn:empty we could simply find-first, i.e. any match - how about calling index.matchElementsByTagName instead, or can we shortcut the search with a node selector? Perhaps update the NodeSelector interface to permit this and then create a FirstNodeSelector(nodeSelector) implementation that can wrap any other NodeSelector? currentSet = index.findElementsByTagName(ElementValue.ELEMENT, docs, test.getName(), nodeSelector, this); currentDocs = docs; diff --git a/exist-core/src/main/java/org/exist/xquery/PrecedingSiblingSelector.java b/exist-core/src/main/java/org/exist/xquery/PrecedingSiblingSelector.java index 81fe73be06c..96116f746c2 100644 --- a/exist-core/src/main/java/org/exist/xquery/PrecedingSiblingSelector.java +++ b/exist-core/src/main/java/org/exist/xquery/PrecedingSiblingSelector.java @@ -37,6 +37,10 @@ import org.exist.dom.persistent.NodeSet; import org.exist.numbering.NodeId; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * A node selector for preceding siblings. * @@ -45,15 +49,108 @@ public class PrecedingSiblingSelector implements NodeSelector { private final NodeSet contextSet; + private final int contextId; + private @Nullable final Expression expression; + +// private @Nullable List followingSiblings; + private @Nullable List precedingSiblings; - public PrecedingSiblingSelector(final NodeSet contextSet) { + public PrecedingSiblingSelector(final NodeSet contextSet, final int contextId, @Nullable final Expression expression) { this.contextSet = contextSet; + this.contextId = contextId; + this.expression = expression; } +// @Override +// public @Nullable NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { +// final @Nullable NodeProxy followingSibling = contextSet.containsFollowingSiblingOf(doc, nodeId); +// if (followingSibling != null) { +// final NodeProxy precedingSibling = new NodeProxy(expression, doc, nodeId); +// if (Expression.IGNORE_CONTEXT != contextId) { +// if (Expression.NO_CONTEXT_ID == contextId) { +// precedingSibling.copyContext(followingSibling); +// } else { +// precedingSibling.addContextNode(contextId, followingSibling); +// } +// } +// return precedingSibling; +// } +// return null; +// } + +// @Override +// public @Nullable NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { +// final @Nullable NodeProxy followingSibling = contextSet.containsFollowingSiblingOf(doc, nodeId); +// if (followingSibling != null) { +// +// if (followingSiblings == null) { +// this.followingSiblings = new ArrayList<>(); +// } +// followingSiblings.add(followingSibling); +// +// final NodeProxy precedingSibling = new NodeProxy(expression, doc, nodeId); +// +// System.out.println("currentId=" + precedingSibling.getNodeId()); +// +// if (Expression.IGNORE_CONTEXT != contextId) { +// int i = 0; +// for (final NodeProxy contextNode : followingSiblings) { +// System.out.println("\t\tSTART INNER LOOP: " + i); +// +// if (Expression.NO_CONTEXT_ID == contextId) { +// System.out.println("\t\t\t" + precedingSibling.getNodeId().toString() + " .copyContext(" + contextNode.getNodeId().toString() + ")"); +// precedingSibling.copyContext(contextNode); +// } else { +// System.out.println("\t\t\t" + precedingSibling.getNodeId().toString() + " .addContextNode(" + contextId + ", " + contextNode.getNodeId().toString() + ")"); +// precedingSibling.addContextNode(contextId, contextNode); +// } +// +// System.out.println("\t\tEND INNER LOOP: " + i); +// i++; +// } +// } +// return precedingSibling; +// } +// return null; +// } + @Override - public NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { - if (contextSet.containsFollowingSiblingOf(doc, nodeId)) { - return new NodeProxy(doc, nodeId); + public @Nullable NodeProxy match(final DocumentImpl doc, final NodeId nodeId) { + final @Nullable NodeProxy followingSibling = contextSet.containsFollowingSiblingOf(doc, nodeId); + if (followingSibling != null) { + +// if (followingSiblings == null) { +// this.followingSiblings = new ArrayList<>(); +// } +// followingSiblings.add(followingSibling); + + final NodeProxy newPrecedingSibling = new NodeProxy(expression, doc, nodeId); + +// System.out.println("currentId=" + newPrecedingSibling.getNodeId()); + + if (precedingSiblings == null) { + this.precedingSiblings = new ArrayList<>(); + } + precedingSiblings.add(newPrecedingSibling); + + if (Expression.IGNORE_CONTEXT != contextId) { +// int i = 0; + for (final NodeProxy precedingSibling : precedingSiblings) { +// System.out.println("\t\tSTART INNER LOOP: " + i); + + if (Expression.NO_CONTEXT_ID == contextId) { +// System.out.println("\t\t\t" + precedingSibling.getNodeId().toString() + " .copyContext(" + followingSibling.getNodeId().toString() + ")"); + precedingSibling.copyContext(followingSibling); + } else { +// System.out.println("\t\t\t" + precedingSibling.getNodeId().toString() + " .addContextNode(" + contextId + ", " + followingSibling.getNodeId().toString() + ")"); + precedingSibling.addContextNode(contextId, followingSibling); + } + +// System.out.println("\t\tEND INNER LOOP: " + i); +// i++; + } + } + return newPrecedingSibling; } return null; } diff --git a/exist-core/src/main/java/org/exist/xquery/Profiler.java b/exist-core/src/main/java/org/exist/xquery/Profiler.java index e900a6f9229..38ce80e822d 100644 --- a/exist-core/src/main/java/org/exist/xquery/Profiler.java +++ b/exist-core/src/main/java/org/exist/xquery/Profiler.java @@ -38,7 +38,7 @@ * logger. The profiler can be enabled/disabled and configured * via an XQuery pragma or "declare option" expression. Example: * - *
declare option exist:profiling "enabled=yes verbosity=10 logger=profiler";
+ *
declare option exist:profiling "enabled=yes verbosity=10 logger=xquery.profiling";
* * @author wolf * diff --git a/exist-core/src/test/java/org/exist/dom/persistent/NewArrayNodeSetTest.java b/exist-core/src/test/java/org/exist/dom/persistent/NewArrayNodeSetTest.java index bc0fc30afe1..8e78ead0b45 100644 --- a/exist-core/src/test/java/org/exist/dom/persistent/NewArrayNodeSetTest.java +++ b/exist-core/src/test/java/org/exist/dom/persistent/NewArrayNodeSetTest.java @@ -22,12 +22,21 @@ package org.exist.dom.persistent; +import org.exist.numbering.DLN; +import org.exist.numbering.NodeId; import org.exist.xquery.Constants; import org.exist.xquery.value.SequenceIterator; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.CsvSource; + +import javax.annotation.Nullable; + +import java.util.Iterator; import static org.easymock.EasyMock.*; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.*; public class NewArrayNodeSetTest { @@ -93,8 +102,536 @@ public void iterate_loop_skip_loop() { assertEquals(69, count); } + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 3.8", + "3.2, 3.4, 3.6, 3.8, 3.10", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18, 3.20", + }) + public void containsPrecedingSiblingOf_sameLevel_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.8")); + assertNotNull(nextPrecedingSibling); + assertEquals(5, nextPrecedingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.6"), nextPrecedingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.6, 3.2, 3.8, 3.4", + "3.10, 3.6, 3.2, 3.8, 3.4", + "3.2, 3.12, 3.6, 3.8, 3.10, 3.4", + "3.2, 3.4, 3.6, 3.10, 3.8, 3.12, 3.14", + "3.2, 3.6, 3.4, 3.8, 3.16, 3.12, 3.14, 3.10", + "3.10, 3.4, 3.12, 3.8, 3.6, 3.16, 3.2, 3.14, 3.18", + "3.2, 3.8, 3.6, 3.10, 3.16, 3.4, 3.12, 3.14, 3.20, 3.18", + }) + public void containsPrecedingSiblingOf_sameLevel_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.8")); + assertNotNull(nextPrecedingSibling); + assertEquals(5, nextPrecedingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.6"), nextPrecedingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 3.8", + "3.2, 3.4, 3.6, 3.8, 3.10", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18, 3.20", + }) + public void containsPrecedingSiblingOf_sameLevel_sorted_duplicates(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.8")); + assertNotNull(nextPrecedingSibling); + assertEquals(5, nextPrecedingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.6"), nextPrecedingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.8, 3.2, 3.8, 3.4, 3.6", + "3.2, 3.6, 3.4, 3.8, 3.6, 3.10", + "3.6, 3.2, 3.4, 3.6, 3.8, 3.12, 3.8, 3.8, 3.10", + "3.10, 3.12, 3.14, 3.2, 3.6, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8", + "3.2, 3.8, 3.8, 3.4, 3.6, 3.8, 3.10, 3.12, 3.6, 3.14, 3.16", + "3.2, 3.4, 3.8, 3.6, 3.6, 3.8, 3.10, 3.12, 3.8, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.8, 3.6, 3.8, 3.10, 3.12, 3.8, 3.14, 3.16, 3.18, 3.20", + }) + public void containsPrecedingSiblingOf_sameLevel_unsorted_duplicates(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.8")); + assertNotNull(nextPrecedingSibling); + assertEquals(5, nextPrecedingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.6"), nextPrecedingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 4.2", + "3.2, 3.4, 3.6, 4.2, 4.4", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10, 5.2", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10, 5.2, 5.4", + }) + public void containsPrecedingSiblingOf_differentLevels_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("4.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "4.2, 3.4, 3.6, 3.2", + "4.4, 4.2, 3.6, 3.2, 3.4", + "3.2, 4.4, 3.6, 4.2, 3.4, 4.6", + "3.2, 3.4, 4.8, 3.6, 4.2, 4.4, 4.6", + "3.2, 3.4, 4.10, 4.2, 4.4, 3.6, 4.8, 4.12", + "4.4, 4.6, 4.8, 4.10, 5.2, 3.2, 3.4, 3.6, 4.2", + "4.4, 4.6, 4.8, 3.2, 3.4, 3.6, 4.2, 4.10, 5.2, 5.4", + }) + public void containsPrecedingSiblingOf_differentLevels_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("4.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 4.2", + "3.2, 3.4, 3.6, 4.2, 4.6", + "3.2, 3.4, 3.6, 4.2, 4.6, 4.8", + }) + public void containsPrecedingSiblingOf_none_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 4.2, 3.4, 3.6", + "3.2, 4.2, 3.6, 3.6, 4.4", + "3.2, 4.8, 4.2, 3.6, 3.6, 4.4", + }) + public void containsPrecedingSiblingOf_none_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsPrecedingSiblingOf(mockDocument, new DLN("3.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 3.8", + "3.2, 3.4, 3.6, 3.8, 3.10", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18, 3.20", + }) + public void containsFollowingSiblingOf_sameLevel_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNotNull(nextFollowingSibling); + assertEquals(5, nextFollowingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.8"), nextFollowingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.6, 3.2, 3.8, 3.4", + "3.10, 3.6, 3.2, 3.8, 3.4", + "3.2, 3.12, 3.6, 3.8, 3.10, 3.4", + "3.2, 3.4, 3.6, 3.10, 3.8, 3.12, 3.14", + "3.2, 3.6, 3.4, 3.8, 3.16, 3.12, 3.14, 3.10", + "3.10, 3.4, 3.12, 3.8, 3.6, 3.16, 3.2, 3.14, 3.18", + "3.2, 3.8, 3.6, 3.10, 3.16, 3.4, 3.12, 3.14, 3.20, 3.18", + }) + public void containsFollowingSiblingOf_sameLevel_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNotNull(nextFollowingSibling); + assertEquals(5, nextFollowingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.8"), nextFollowingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 3.8", + "3.2, 3.4, 3.6, 3.8, 3.10", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8, 3.10, 3.12, 3.14, 3.16, 3.18, 3.20", + }) + public void containsFollowingSiblingOf_sameLevel_sorted_duplicates(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNotNull(nextFollowingSibling); + assertEquals(5, nextFollowingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.8"), nextFollowingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.8, 3.2, 3.8, 3.4, 3.6", + "3.2, 3.6, 3.4, 3.8, 3.6, 3.10", + "3.6, 3.2, 3.4, 3.6, 3.8, 3.12, 3.8, 3.8, 3.10", + "3.10, 3.12, 3.14, 3.2, 3.6, 3.4, 3.6, 3.6, 3.8, 3.8, 3.8", + "3.2, 3.8, 3.8, 3.4, 3.6, 3.8, 3.10, 3.12, 3.6, 3.14, 3.16", + "3.2, 3.4, 3.8, 3.6, 3.6, 3.8, 3.10, 3.12, 3.8, 3.14, 3.16, 3.18", + "3.2, 3.4, 3.6, 3.8, 3.6, 3.8, 3.10, 3.12, 3.8, 3.14, 3.16, 3.18, 3.20", + }) + public void containsFollowingSiblingOf_sameLevel_unsorted_duplicates(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNotNull(nextFollowingSibling); + assertEquals(5, nextFollowingSibling.getDoc().getDocId()); + assertEquals(new DLN("3.8"), nextFollowingSibling.getNodeId()); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 4.2", + "3.2, 3.4, 3.6, 4.2, 4.4", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10, 5.2", + "3.2, 3.4, 3.6, 4.2, 4.4, 4.6, 4.8, 4.10, 5.2, 5.4", + }) + public void containsFollowingSiblingOf_differentLevels_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNull(nextFollowingSibling); + } + + @ParameterizedTest + @CsvSource({ + "4.2, 3.4, 3.6, 3.2", + "4.4, 4.2, 3.6, 3.2, 3.4", + "3.2, 4.4, 3.6, 4.2, 3.4, 4.6", + "3.2, 3.4, 4.8, 3.6, 4.2, 4.4, 4.6", + "3.2, 3.4, 4.10, 4.2, 4.4, 3.6, 4.8, 4.12", + "4.4, 4.6, 4.8, 4.10, 5.2, 3.2, 3.4, 3.6, 4.2", + "4.4, 4.6, 4.8, 3.2, 3.4, 3.6, 4.2, 4.10, 5.2, 5.4", + }) + public void containsFollowingSiblingOf_differentLevels_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextFollowingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("3.6")); + assertNull(nextFollowingSibling); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 3.4, 3.6, 4.2", + "3.0, 3.2, 3.4, 3.6, 4.2", + "3.0, 3.1, 3.2, 3.4, 3.6, 4.2", + }) + public void containsFollowingSiblingOf_none_sorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("4.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "3.2, 4.1, 3.4, 4.2", + "3.2, 4.1, 3.6, 3.6, 4.2", + "3.2, 4.1, 4.0, 3.6, 3.6, 4.2", + }) + public void containsFollowingSiblingOf_none_unsorted(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + @Nullable final NodeProxy nextPrecedingSibling = newArrayNodeSet.containsFollowingSiblingOf(mockDocument, new DLN("4.2")); + assertNull(nextPrecedingSibling); + } + + @ParameterizedTest + @CsvSource({ + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 4.2, 4.4", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 3.10, 3.12, 3.4", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 4.2, 3.10, 3.12, 4.4, 3.4", + }) + public void precedingSiblingsOf(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + final Iterator it = newArrayNodeSet.precedingSiblingsOf(mockDocument, new DLN("3.8")); + + for (final DLN expected : new DLN[] { new DLN("3.6"), new DLN("3.4"), new DLN("3.2") }) { + assertTrue(it.hasNext()); + final NodeId actual = it.next().getNodeId(); + assertEquals(expected, actual); + } + } + + @ParameterizedTest + @CsvSource({ + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 4.2, 4.4", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 3.10, 3.12, 3.4", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 4.2, 3.10, 3.12, 4.4, 3.4", + }) + public void precedingSiblingsOfReverse(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + final Iterator it = newArrayNodeSet.precedingSiblingsOfReverse(mockDocument, new DLN("3.8")); + + for (final DLN expected : new DLN[] { new DLN("3.2"), new DLN("3.4"), new DLN("3.6") }) { + assertTrue(it.hasNext()); + final NodeId actual = it.next().getNodeId(); + assertEquals(expected, actual); + } + } + + @ParameterizedTest + @CsvSource({ + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 4.2, 4.4, 3.10, 3.12", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 3.10, 3.12, 3.4", + "3.10, 3.12, 3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 4.2, 4.4, 3.4", + }) + public void followingSiblingsOf(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + final Iterator it = newArrayNodeSet.followingSiblingsOf(mockDocument, new DLN("3.6")); + + for (final DLN expected : new DLN[] { new DLN("3.8"), new DLN("3.10"), new DLN("3.12") }) { + assertTrue(it.hasNext()); + final NodeId actual = it.next().getNodeId(); + assertEquals(expected, actual); + } + } + + @ParameterizedTest + @CsvSource({ + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 3.10, 3.12", + "2.2, 2.4, 2.5, 3.2, 3.4, 3.6, 3.8, 4.2, 4.4, 3.10, 3.12", + "3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 3.10, 3.12, 3.4", + "3.10, 3.12, 3.2, 2.4, 2.5, 2.2, 3.6, 3.8, 4.2, 4.4, 3.4", + }) + public void followingSiblingsOfReverse(final ArgumentsAccessor argumentsAccessor) { + final DocumentImpl mockDocument = createMock(DocumentImpl.class); + expect(mockDocument.getDocId()).andReturn(5).atLeastOnce(); + expect(mockDocument.getExpression()).andReturn(null).atLeastOnce(); + replay(mockDocument); + + final NewArrayNodeSet newArrayNodeSet = new NewArrayNodeSet(); + for (int i = 0; i < argumentsAccessor.size(); i++) { + final String nodeIdStr = argumentsAccessor.getString(i); + newArrayNodeSet.add(new NodeProxy(mockDocument, new DLN(nodeIdStr))); + } + + final Iterator it = newArrayNodeSet.followingSiblingsOfReverse(mockDocument, new DLN("3.6")); + + for (final DLN expected : new DLN[] { new DLN("3.12"), new DLN("3.10"), new DLN("3.8") }) { + assertTrue(it.hasNext()); + final NodeId actual = it.next().getNodeId(); + assertEquals(expected, actual); + } + } + private static NewArrayNodeSet mockNewArrayNodeSet(final int size) { - final NodeProxy mockNodes[] = new NodeProxy[size]; + final NodeProxy[] mockNodes = new NodeProxy[size]; for (int i = 0; i < mockNodes.length; i++) { final NodeProxy mockNodeProxy = createMock(NodeProxy.class); replay(mockNodeProxy); diff --git a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java index fce6545f695..207c5ad214d 100644 --- a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java +++ b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java @@ -72,7 +72,7 @@ public class XPathQueryTest { public static java.util.Collection data() { return Arrays.asList(new Object[][] { { "local", XmldbURI.LOCAL_DB }, - { "remote", "xmldb:exist://localhost:" + PORT_PLACEHOLDER + "/xmlrpc" + XmldbURI.ROOT_COLLECTION} +// { "remote", "xmldb:exist://localhost:" + PORT_PLACEHOLDER + "/xmlrpc" + XmldbURI.ROOT_COLLECTION} }); } @@ -567,7 +567,8 @@ public void precedingSiblingAxis_persistent() throws XMLDBException, IOException service.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); service.setProperty(OutputKeys.INDENT, "no"); - ResourceSet result = queryResource(service, "siblings.xml", "//a[preceding-sibling::*[1]/s = 'B']", 1); + ResourceSet result; + result = queryResource(service, "siblings.xml", "//a[preceding-sibling::*[1]/s = 'B']", 1); assertXMLEqual(" Z 4 ", result.getResource(0).getContent().toString()); result = queryResource(service, "siblings.xml", "//a[preceding-sibling::a[1]/s = 'B']", 1); @@ -576,6 +577,7 @@ public void precedingSiblingAxis_persistent() throws XMLDBException, IOException result = queryResource(service, "siblings.xml", "//a[preceding-sibling::*[2]/s = 'B']", 1); assertXMLEqual(" C 5 ", result.getResource(0).getContent().toString()); + // TODO(AR) this one was problematic result = queryResource(service, "siblings.xml", "//a[preceding-sibling::a[2]/s = 'B']", 1); assertXMLEqual(" C 5 ", result.getResource(0).getContent().toString()); @@ -687,7 +689,8 @@ public void followingSiblingAxis_persistent() throws XMLDBException, IOException service.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); service.setProperty(OutputKeys.INDENT, "no"); - ResourceSet result = queryResource(service, "siblings.xml", "//a[following-sibling::*[1]/s = 'B']", 1); + ResourceSet result; + result = queryResource(service, "siblings.xml", "//a[following-sibling::*[1]/s = 'B']", 1); assertXMLEqual(" Z 2 ", result.getResource(0).getContent().toString()); result = queryResource(service, "siblings.xml", "//a[following-sibling::a[1]/s = 'B']", 1); @@ -696,6 +699,7 @@ public void followingSiblingAxis_persistent() throws XMLDBException, IOException result = queryResource(service, "siblings.xml", "//a[following-sibling::*[2]/s = 'B']", 1); assertXMLEqual(" A 1 ", result.getResource(0).getContent().toString()); + // TODO(AR) this one was problematic result = queryResource(service, "siblings.xml", "//a[following-sibling::a[2]/s = 'B']", 1); assertXMLEqual(" A 1 ", result.getResource(0).getContent().toString());