All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.api.util.RowTree Maven / Gradle / Ivy

The newest version!
package com.almworks.jira.structure.api.util;

import com.almworks.integers.*;
import com.almworks.jira.structure.api.forest.raw.ArrayForest;
import com.almworks.jira.structure.api.forest.raw.Forest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.function.LongPredicate;

/**
 * 

RowTree is another representation of the Forest concept. It is implemented as a tree structure and is * an alternative to ArrayForest when it comes to manipulating the forest.

* *

RowTree is far more heavy on memory and GC - each row is a separate object + 52 bytes of payload. However, * it is very fast with the modification operations.

* *

Suggested use:

*
    *
  • Do not use for long-term forest storage!
  • *
  • Do not use when ArrayForest is sufficient for the modifications you need.
  • *
  • Do not use when you can arrange the modifications you need in a bulk array-creation fashion.
  • *
  • DO use as a temporary object when you need a (potentially big) number of random changes with a forest and * none of the above applies.
  • *
* *

Additional concepts and terms:

*
    *
  • Super-root is the "hidden" node that all root nodes are children of.
  • *
  • There's no concept of depth! Depth can be calculated by going up the parent chain, if needed. But it * shouldn't be needed.
  • *
  • Flags field lets you store temporary bitmask data in each node to help your algorithms. Semantics is * up to you.
  • *
* *

Note that this class does not check invariants (like uniqueness of row IDs).

* *

Not thread-safe.

* *

This method does not and should not implement {@code Forest} because it does not provide indexed access.

* * @see RowTree.Node * @see IndexedRowTree */ public class RowTree { private final Node mySuperRoot = new Node(0, 0); public Node getSuperRoot() { return mySuperRoot; } @Override public String toString() { return mySuperRoot.toFullString(); } /** * Appends forest to the end of the tree. Forest roots also become roots in the RowTree. * @param forest the forest */ public void appendForest(@NotNull Forest forest) { appendForest(forest, mySuperRoot, mySuperRoot.getLastChild(), -1, 0, (LongPredicate) null); } /** * Appends forest at the specified place. * @param forest the forest * @param underNode the would-be parent * @param afterNode the would-be preceding sibling, or null if the inserted nodes should come first under {@code underNode} */ public void appendForest(@NotNull Forest forest, @NotNull Node underNode, @Nullable Node afterNode) { appendForest(forest, underNode, afterNode, -1, 0, (LongPredicate) null); } /** * Generic utility method for adding the forest to the RowTree, with parameters for optimization. * * @param forest the source forest * @param parentNode the would-be parent * @param afterNode the would-be preceding sibling, or null if the inserted nodes should come first under {@code underNode} * @param parentIndex the index of the source row in {@code forest} - only children of of that row and their sub-trees are * added (the row itself is not added); use -1 to insert the whole forest * @param flags initial flags for all created nodes * @param rowFilter when not null, each node is tested with this predicate; failed nodes and their sub-trees are skipped * (even if some row in the sub-tree passes the filter) * @return the index in the forest where iteration stopped (subtreeEnd for the parentNode) */ public int appendForest(@NotNull Forest forest, @NotNull Node parentNode, @Nullable Node afterNode, int parentIndex, int flags, @Nullable LongPredicate rowFilter) { assert afterNode == null || afterNode.getParent() == parentNode : parentNode + " " + afterNode; // when we go higher than minDepth, stop iteration int minDepth = parentIndex < 0 ? 0 : forest.getDepth(parentIndex) + 1; // we will walk the tree with currentNode/currentDepth Node currentNode = afterNode; int currentDepth = minDepth; if (currentNode == null) { currentNode = parentNode; currentDepth--; } int size = forest.size(); int i = parentIndex + 1; while (i < size) { int depth = forest.getDepth(i); if (depth < minDepth) { // end of children and their subtrees break; } long rowId = forest.getRow(i); if (rowFilter != null && !rowFilter.test(rowId)) { // skip subtree i = forest.getSubtreeEnd(i); } else { Node node = createNode(rowId, flags); if (currentDepth < depth) { // stepping down the depth assert depth == currentDepth + 1 : "forest invariant broken @" + i + ": depth " + currentDepth + " => " + depth; currentNode = currentNode.insertChild(node); currentDepth++; } else { while (currentDepth > depth) { // stepping up the depth currentNode = currentNode.getParent(); currentDepth--; } currentNode = currentNode.appendSibling(node); } i++; } } return i; } /** * Adds a node to the tree at the specified position. * * @param rowId row ID * @param parentNode the would-be parent * @param afterNode the would-be preceding sibling, or null if the inserted nodes should come first under {@code underNode} * @param flags initial flags for all created nodes * * @return inserted node */ public Node insertNode(long rowId, int flags, Node parentNode, Node afterNode) { return insertNaturalizedNode(createNode(rowId, flags), parentNode, afterNode); } /** * Moves node within a tree * * @param node node to move * @param parentNode the would-be parent * @param afterNode the would-be preceding sibling, or null if the inserted nodes should come first under {@code underNode} * * @return node */ public Node moveNode(Node node, Node parentNode, Node afterNode) { return insertNaturalizedNode(node.pluck(), parentNode, afterNode); } /** * Removes node and its sub-tree from the tree. * * @param node node */ public void remove(Node node) { if (node == mySuperRoot) throw new IllegalArgumentException("cannot remove super-root"); node.pluck(); forgetNode(node); } /** * Creates {@link Forest} representing this RowTree. * * @return ArrayForest */ public ArrayForest toForest() { LongArray rows = new LongArray(); IntArray depths = new IntArray(); mySuperRoot.writeTo(rows, depths, -1); return new ArrayForest(rows, depths, true); } /** * Called always to create a new node for insertion in this tree. * Intended to be overridden with additional checks and maintaining state. */ protected Node createNode(long rowId, int flags) { return new Node(rowId, flags); } /** * Called always when a node is removed (with its sub-nodes). Not called for sub-nodes explicitly. * Intended to be overridden with additional checks and maintaining state. */ protected void forgetNode(Node node) { } /** * "Naturalized" means that the node has been created with createNode() in this tree and was not yet * forgotten with forgetNode(). */ private Node insertNaturalizedNode(Node node, Node parentNode, Node afterChild) { assert afterChild == null || afterChild.getParent() == parentNode : parentNode + " " + afterChild; if (afterChild != null) { afterChild.appendSibling(node); } else { parentNode.insertChild(node); } return node; } /** *

Represents a single node or a {@link RowTree}. You can traverse the tree up/down/left/right from a single node.

* *

A node may be "unplugged", in which case its {@link #getParent()} is null. (Root nodes have RowTree's super-root as * the parent.)

* *

Public methods of {@code Node} are provided for traveral. All modifications should be done through {@code RowTree}.

*/ public static class Node { private final long myRowId; private Node myParent; private Node myFirstChild; private Node myLastChild; private Node myNextSibling; private Node myPrevSibling; // user-specific flags private int myFlags; private Node(long rowId, int flags) { myRowId = rowId; myFlags = flags; } @Override public String toString() { return myFlags == 0 ? String.valueOf(myRowId) : myRowId + "#" + myFlags; } /** * @return lisp-style full printout */ public String toFullString() { return append(new StringBuilder()).toString(); } public StringBuilder append(StringBuilder sb) { if (myRowId != 0) sb.append(myRowId); if (myFirstChild != null) { sb.append('('); for (Node n = myFirstChild; n != null; n = n.myNextSibling) { if (n != myFirstChild) sb.append(','); n.append(sb); } sb.append(')'); } return sb; } public long getRowId() { return myRowId; } /** * Bitwise flag check. * * @param flagMask mask * @return true if all flags specified in the mask are set */ public boolean hasFlags(int flagMask) { return (myFlags & flagMask) == flagMask; } /** * Sets flags by mask. * * @param flagMask mask */ public void addFlags(int flagMask) { myFlags |= flagMask; } public Node getParent() { assert myParent != null; return myParent; } public Node getFirstChild() { return myFirstChild; } public Node getLastChild() { return myLastChild; } public Node getNextSibling() { return myNextSibling; } public Node getPrevSibling() { return myPrevSibling; } /** * Dumps the sub-tree rooted at this node as parallel lists of rows and depths, to be later used * as parameters for {@link ArrayForest}. * * @param rows rows recipient * @param depths depths recipient * @param depth starting depth for this node; if -1, this is a special case: this node is a super-root and * must not write out its row ID (which is 0), but should proceed writing out children. */ public void writeTo(LongCollector rows, IntCollector depths, int depth) { if (depth >= 0) { assert myRowId != 0; rows.add(myRowId); depths.add(depth); } for (Node n = myFirstChild; n != null; n = n.myNextSibling) { n.writeTo(rows, depths, depth + 1); } } /** * Retrieves all direct children of the node * * @param collector recipient for row IDs */ public void collectDirectChildren(LongCollector collector) { for (Node n = myFirstChild; n != null; n = n.myNextSibling) { collector.add(n.getRowId()); } } /** * Starting with this node and going into its sub-tree, tries to find the first node that has the specified * flags set. Walks in LNR depth-first order. * * @param flags flags to look for * * @return node with the flags or null if not found */ public Node findFirstWithFlags(int flags) { if (hasFlags(flags)) return this; for (Node n = myFirstChild; n != null; n = n.myNextSibling) { Node r = n.findFirstWithFlags(flags); if (r != null) return r; } return null; } // inserts node as the first child of this node private Node insertChild(Node node) { assert node.myParent == null; assert node.myNextSibling == null; assert node.myPrevSibling == null; if (myFirstChild == null) { assert myLastChild == null; myLastChild = node; } else { node.myNextSibling = myFirstChild; myFirstChild.myPrevSibling = node; } myFirstChild = node; node.myParent = this; return node; } // inserts node as the next sibling of this node private Node appendSibling(Node node) { assert node.myParent == null; assert node.myNextSibling == null; assert node.myPrevSibling == null; if (myNextSibling == null && myParent != null) { myParent.myLastChild = node; } node.myNextSibling = myNextSibling; if (myNextSibling != null) { myNextSibling.myPrevSibling = node; } myNextSibling = node; node.myPrevSibling = this; node.myParent = myParent; return node; } /** * Removes node (and its sub-tree) from the RowTree. Note that unless the node is reinserted in the same * transaction, you should call {@link RowTree#forgetNode}. * * @return this node in "unplugged" state */ private Node pluck() { if (myPrevSibling != null) { myPrevSibling.myNextSibling = myNextSibling; } else { myParent.myFirstChild = myNextSibling; } if (myNextSibling != null) { myNextSibling.myPrevSibling = myPrevSibling; } else { myParent.myLastChild = myPrevSibling; } myPrevSibling = null; myNextSibling = null; myParent = null; return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy