Interface Forest

All Superinterfaces:
Iterable<LongIntIterator>, LongIntIterable
All Known Implementing Classes:
ArrayForest

public interface Forest extends LongIntIterable

Forest interface provides indexed access to a data structure that stores row hierarchy (the hierarchy is a sequence of tree structures, a forest). That is, you can read row IDs and depths given that you know the index of the pair in the sequence. There are also a number of utility methods that allow searching and browsing the forest.

To get the details of a specific row (such as issue ID), you need to obtain StructureRow from RowManager.

Forest of rows is represented as a sequence of pairs (long row, int depth). The hierarchy is identified by the position in the sequence and the associated depth. For example, the following hierarchy:

     A
        A1
        A2
     B
        B1
            B1X
   
is represented in the forest as the following sequence:
     (A, 0), (A1, 1), (A2, 1), (B, 0), (B1, 1), (B1X, 2)
   

Invariants

Because depth associated with a row is an integer number, there are certain invariants about the depth that must hold true, otherwise the data does not make sense. Additionally, a row may appear no more than once in the forest. Make sure you don't violate those invariants when creating new forest instances:

  • The depth of the first element in the forest must be 0.
  • Given that the depth of an element is D, the depth of the following element must be within range [0 .. D+1].
  • If a row is present in the forest at index N, it must not be present at any other index.
  • Row ID may not be 0 - this value represents "missing" or "no value" in various methods of this interface.

When Java assertions are enabled, these invariants are checked all the time. Due to performance impact, the invariants are not checked and assumed to hold true when running with assertions disabled. The result of creating and using a forest that violates these invariants is undefined.

Super-root

The super-root row (SuperRootRow) has a predefined row ID of -1. This row id is reserved and cannot be used for normal rows.

Author:
Igor Sereda
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final Forest
     
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    containsRow(long row)
    Can be used to check if the forest contains a specific row.
    Creates an exact copy of this forest.
    copySubforest(long row)
     
     
    filter(La<Long,?> filter)
    Filters this forest by hiding rows that do not pass the filter condition.
    filterHardest(La<Long,?> filter)
    Filters this forest by excluding rows that do not pass the filter condition.
    filterSoft(La<Long,?> filter)
    Filters this forest by excluding rows that do not pass the filter condition.
    <T, C> C
    This is a more generic version of visitParentChildrenUpwards(com.almworks.jira.structure.api.forest.raw.ForestParentChildrenVisitor) that allows you to effectively process the forest bottom-up, probably saving on memory allocation and search speed.
    LongArray
    getChildren(long row)
    Creates an array with all direct sub-rows of the specified row.
    LongArray
    getChildrenAtIndex(int index)
    Creates an array with all direct sub-rows of the row at the specified index.
    IntIterator
    Returns an iterator over indices of all direct sub-rows of the row at the specified index.
    int
    getDepth(int index)
    Gets the depth of the row at the specified position in the forest.
    IntList
    Returns a non-modifiable list of depths, in the order the rows appear in the forest.
    long
    getLastChild(long parent)
    Gets the last direct sub-row of the specified parent row.
    long
    getLastChildByIndex(int parentIndex)
    Gets the last direct sub-row of the specified parent row.
    long
    getNextSibling(long row)
    Gets the row that immediately follows the specified row in the list of children of the specified row's parent.
    long
    Gets the row that immediately follows the one with the given index in the list of children of its parent.
    int
    Gets the index of the row that immediately follows the row at the given index in the list of children of its parent.
    long
    getParent(long row)
    Gets the parent row of the specified row.
    int
    getParentIndex(int index)
    Searches the forest for the index of a "parent row".
    LongArray
    Returns the path to the specified row without the row itself.
    LongArray
    getPath(long row)
    Returns the path to the specified row - a sequence of rows that starts with a root row, ends with the specified row, and where element[i] is the parent row of element[i+1].
    LongArray
    getPathForIndex(int index)
    Similar to getPath(long), returns the path to the row specified by index.
    int
    getPathIndexAtDepth(int index, int depth)
    Given row at the specified index, traverses its "path" upwards - that is, looks for all parent rows up to the topmost root parent, and returns an index of the parent that has the specified depth.
    long
    Gets the row that immediately precedes the specified row in the list of children of the specified row's parent.
    long
    Gets the row that immediately precedes the one with the given index in the list of children of its parent.
    int
    Gets the index of the row that immediately precedes the row at the given index in the list of children of its parent.
    LongArray
    Returns the array of all rows that come before the given row in the list of children of its parent, in the same order as they appear in the forest.
    LongArray
    Returns the array of all rows that come before the given row in the list of children of its parent, in the same order as they appear in the forest.
    LongArray
    Returns an array of all root rows in the forest (those that have depth of 0).
    long
    getRow(int index)
    Gets the ID of the row at the specified position in the forest.
    LongList
    Returns a non-modifiable list of rows, in the order they appear in the forest.
    int
    getSubtreeEnd(int index)
    The method looks for the end of a subtree rooted at the specified index.
    int
    indexOf(long row)
    Searches for the position of a specific row in the forest.
    boolean
    Used to check if the forest does not contain any rows.
    boolean
    Returns true if the forest is immutable.
    void
    Iterates through each row from top to the bottom.
    int
    Returns the size of the forest, the number of rows in it, the number is always >= 0.
    subtree(long row)
    Creates a forest that contains the specified row and all its sub-rows from this forest.
    subtreeAtIndex(int index)
    Creates a forest that contains the specified row and all its sub-rows from this forest.
    Utility method for debugging - returns full string representation of the forest, that contains all the information, unlike toString() method, which may be truncated to some character number limit.
    void
    This method is used to efficiently traverse all pairs (parent, children) from the end of the forest upwards.

    Methods inherited from interface Iterable

    forEach, spliterator

    Methods inherited from interface LongIntIterable

    iterator
  • Field Details

    • EMPTY

      static final Forest EMPTY
  • Method Details

    • size

      int size()
      Returns the size of the forest, the number of rows in it, the number is always >= 0.
      Returns:
      the size of the forest
    • isEmpty

      boolean isEmpty()
      Used to check if the forest does not contain any rows.
      Returns:
      true if the size of the forest is zero
    • getRows

      @NotNull LongList getRows()
      Returns a non-modifiable list of rows, in the order they appear in the forest. The size of the list is equal to the value returned by size().
      Returns:
      list of IDs of rows contained in the forest
    • getDepths

      @NotNull IntList getDepths()
      Returns a non-modifiable list of depths, in the order the rows appear in the forest. The size of the list is equal to the value returned by size() and the i-th element of this list corresponds to the row ID at the i-th position in the list returned by getRows().
      Returns:
      list of depths of rows contained in the forest, parallel to the rows list
    • getRow

      long getRow(int index)
      Gets the ID of the row at the specified position in the forest.
      Parameters:
      index - the index of the forest entry
      Returns:
      row ID at the specified index
      Throws:
      IndexOutOfBoundsException - if index is not within range [0 .. size() - 1].
    • getDepth

      int getDepth(int index)
      Gets the depth of the row at the specified position in the forest.
      Parameters:
      index - the index of the forest entry
      Returns:
      depth at the specified index, value is always >= 0 and satisfies the invariants for the Forest
      Throws:
      IndexOutOfBoundsException - if index is not within range [0 .. size() - 1].
    • isImmutable

      boolean isImmutable()
      Returns true if the forest is immutable.
      See Also:
    • indexOf

      int indexOf(long row)

      Searches for the position of a specific row in the forest.

      Execution of this method may take O(size()) time, however it may be optimized if the implementation maintains an index. It's better to use this method rather than using forest.getRows().indexOf(row) because of the possible optimizations.

      Parameters:
      row - the row ID to search for
      Returns:
      such index of the row in the forest, or -1 if the row is not found
    • containsRow

      boolean containsRow(long row)

      Can be used to check if the forest contains a specific row.

      Execution of this method may take O(size()) time.

      Parameters:
      row - the row ID to search for
      Returns:
      true if the forest contains the specified row
    • getSubtreeEnd

      int getSubtreeEnd(int index)

      The method looks for the end of a subtree rooted at the specified index.

      A subtree rooted at index k is a sub-sequence in the forest starting at position k and containing all following elements that have depth d > depth[k].

      The result of this method is the next index after the last element of this subsequence. More specifically, the result is the first element after [k] that has depth d <= depth[k].

      When the subtree ends with the whole forest, the return value is equal to size().

      If index is a negative number, the returned value is 0. If index is greater or equal than size(), the returned value is equal to size().

      Parameters:
      index - the index of the root row of the subtree
      Returns:
      the index of the row that follows the last row of the subtree, or the size of the forest in case the subtree is at the end of it
    • getParentIndex

      int getParentIndex(int index)

      Searches the forest for the index of a "parent row". If the row at the specified index is a root row (has depth of 0), returns -1.

      If index is negative, returns -1.

      Parameters:
      index - index of a child row
      Returns:
      -1 if the row at index is a root row, or index k of the parent row, such as that k = MAX(k in [0, index-1] that has depth[k] == depth[index] - 1)
      Throws:
      IndexOutOfBoundsException - if index is equal or greater than forest size
    • getParent

      long getParent(long row)
      Gets the parent row of the specified row.
      Parameters:
      row - child row
      Returns:
      parent row, or 0 if the child row is a root row or not found in the forest
    • getPrecedingSibling

      long getPrecedingSibling(long row)
      Gets the row that immediately precedes the specified row in the list of children of the specified row's parent.
      Parameters:
      row - a row
      Returns:
      immediately preceding sibling (row that has the same parent and same depth), or 0 if row is not found in the forest or is the first root or the first child of its parent row
    • getPrecedingSiblingIndex

      int getPrecedingSiblingIndex(int index)
      Gets the index of the row that immediately precedes the row at the given index in the list of children of its parent.
      Parameters:
      index - row index
      Returns:
      index of immediately preceding sibling, or -1 if row at index is not found in the forest or is the first root or the first child of its parent row
    • getPrecedingSiblingForIndex

      long getPrecedingSiblingForIndex(int index)
      Gets the row that immediately precedes the one with the given index in the list of children of its parent.
      Parameters:
      index - row index
      Returns:
      immediately preceding sibling, or 0 if there is none
    • getPrecedingSiblings

      @NotNull LongArray getPrecedingSiblings(long row)
      Returns the array of all rows that come before the given row in the list of children of its parent, in the same order as they appear in the forest.
      Parameters:
      row - a row
      Returns:
      array of all preceding siblings, empty if row has none or is not in the forest
    • getPrecedingSiblingsForIndex

      @NotNull LongArray getPrecedingSiblingsForIndex(int index)
      Returns the array of all rows that come before the given row in the list of children of its parent, in the same order as they appear in the forest.
      Parameters:
      index - row index
      Returns:
      array of all preceding siblings, empty if row at index has none or index is negative
    • getNextSiblingIndex

      int getNextSiblingIndex(int index)
      Gets the index of the row that immediately follows the row at the given index in the list of children of its parent.
      Parameters:
      index - row index
      Returns:
      index of immediately following sibling, or -1 if row at index is not found in the forest or is the last root or the last child of its parent row
    • getNextSiblingForIndex

      long getNextSiblingForIndex(int index)
      Gets the row that immediately follows the one with the given index in the list of children of its parent.
      Parameters:
      index - row index
      Returns:
      immediately following sibling, or 0 if there is none
    • getNextSibling

      long getNextSibling(long row)
      Gets the row that immediately follows the specified row in the list of children of the specified row's parent.
      Parameters:
      row - a row
      Returns:
      immediately following sibling (row that has the same parent and same depth), or 0 if row is not found in the forest or is the last root or the last child of its parent row
    • getPathIndexAtDepth

      int getPathIndexAtDepth(int index, int depth)

      Given row at the specified index, traverses its "path" upwards - that is, looks for all parent rows up to the topmost root parent, and returns an index of the parent that has the specified depth.

      If the required depth is equal to the depth of the row at the specified index, returns index.

      If index is a negative value, returns -1. If row at the specified index has less depth than the required value, returns -1.

      Parameters:
      index - index of the row
      depth - required depth of the [grand-] parent row
      Returns:
      the index of the [grand-] parent row on the "path" to the specified row that has the specified depth, or -1 if not found
      Throws:
      IndexOutOfBoundsException - if index is equal or greater than the size of the forest
    • getLastChild

      long getLastChild(long parent)

      Gets the last direct sub-row of the specified parent row.

      Special case: when parent is 0, returns the last root row in the forest.

      If the parent row is not in the forest, or if it does not have child rows, the return value is 0.

      Parameters:
      parent - parent row
      Returns:
      the last row among the parent row's children, or 0 if the parent row does not have children
    • getLastChildByIndex

      long getLastChildByIndex(int parentIndex)

      Gets the last direct sub-row of the specified parent row.

      Special case: when parentIndex is less than zero, returns the last root row in the forest.

      If the parent row does not have child rows, the return value is 0.

      Parameters:
      parentIndex - the index of the parent row
      Returns:
      the last row among the parent row's children, or 0 if the parent row does not have children
    • getChildrenIndicesIterator

      @NotNull IntIterator getChildrenIndicesIterator(int index)

      Returns an iterator over indices of all direct sub-rows of the row at the specified index.

      This method is a lazy variant of getChildrenAtIndex(int): the underlying data structure is scanned as you advance the returned iterator. This allows to save on an extra scan, and on copying to the result array.

      Another difference is that this method returns indices, not the rows themselves.

      Parameters:
      index - the index of the parent row, in the interval [-1; forest.size()); if -1, will iterate over roots
      Returns:
      iterator over indices of all sub-rows of the specified row that have depth equal to the parent depth + 1, or over indices of roots if index is -1
      Throws:
      IndexOutOfBoundsException - if the index is greater or equals the size of the forest
    • getChildren

      @NotNull LongArray getChildren(long row)

      Creates an array with all direct sub-rows of the specified row.

      The returned array is writable and owned by the calling code.

      If row is not in the forest or does not have children, empty array is returned.

      Parameters:
      row - the parent row, you can pass 0 to get the roots.
      Returns:
      array of all sub-rows of the specified row that have depth equal to the parent depth + 1
    • getChildrenAtIndex

      @NotNull LongArray getChildrenAtIndex(int index)

      Creates an array with all direct sub-rows of the row at the specified index.

      The returned array is writable and owned by the calling code.

      If the specified row does not have children, or if the index is negative, empty array is returned.

      Parameters:
      index - the index of the parent row
      Returns:
      array of all sub-rows of the specified row that have depth equal to the parent depth + 1
      Throws:
      IndexOutOfBoundsException - if the index is greater or equals the size of the forest
    • getRoots

      @NotNull LongArray getRoots()
      Returns an array of all root rows in the forest (those that have depth of 0).
      Returns:
      an array of all root rows in the forest (those that have depth of 0)
    • getPath

      @NotNull LongArray getPath(long row)

      Returns the path to the specified row - a sequence of rows that starts with a root row, ends with the specified row, and where element[i] is the parent row of element[i+1].

      If row is not in the forest, returns empty array.

      The array is modifiable and owned by the calling code.

      Parameters:
      row - row to get the path to
      Returns:
      path to the specified row, or empty array if the row is not in the forest
    • getPathForIndex

      @NotNull LongArray getPathForIndex(int index)
      Similar to getPath(long), returns the path to the row specified by index.
      Parameters:
      index - the index of the row to get the path to
      Returns:
      path to the specified row, or empty array if the index is negative
      Throws:
      NoSuchElementException - if the index is greater than or equal to forest size
    • getParentPathForIndex

      @NotNull LongArray getParentPathForIndex(int index)

      Returns the path to the specified row without the row itself. In other words, this is the path to the parent of the specified row, if there is one.

      Parameters:
      index - row index
      Returns:
      path to the specified row's parent; empty array if the row is top-level or not in the forest
      See Also:
    • filter

      @NotNull Forest filter(@Nullable La<Long,?> filter)

      Filters this forest by hiding rows that do not pass the filter condition. The resulting forest contains only the rows that pass the condition (all of them).

      The topology of the resulting forest may differ from the original forest - that is, a row may have a different parent in the resulting forest. This happens when a row that has sub-rows is filtered out - in that case, its sub-tree is substituted instead of the parent row. This is different from filterSoft(com.almworks.jira.structure.api.util.La<java.lang.Long, ?>) method, which preserves the topology.

      This forest is not modified by this method. If all rows pass the condition, then this forest is returned as the result. If filtering has taken place, a new forest is returned.

      The filter method is called once for every row in the forest, and a truthy result (as defined in La.accepts(T)) indicates that the row passes the filter.

      Parameters:
      filter - filter that returns a truthy value if the row with given ID is allowed to be present in the resulting forest. Null means "no filtering" - this forest is returned
      Returns:
      a filtered forest (or this forest if all rows satisfy the filter)
      See Also:
    • filterSoft

      @NotNull Forest filterSoft(@Nullable La<Long,?> filter)

      Filters this forest by excluding rows that do not pass the filter condition. All rows that contain sub-rows that have passed the filter are also preserved. The resulting forest contains sub-sequence of the original forest with rows having the same parents and depths.

      Unlike filter(com.almworks.jira.structure.api.util.La<java.lang.Long, ?>) method, this method preserves the topology of the original forest - all rows in the resulting forest have the same root path as they do in the original forest.

      This forest is not modified by this method. If all rows pass the condition, then this forest is returned as the result. If filtering has taken place, a new forest is returned.

      The filter method is called once for every row in the forest, and a truthy result (as defined in La.accepts(T)) indicates that the row passes the filter.

      Note: if you need to filter by hierarchy-based or JQL constraints, see StructureQuery.

      Parameters:
      filter - filter that returns a truthy value if a row with given ID should be present in the resulting forest Null means "no filtering" - this forest is returned
      Returns:
      a filtered forest (or this forest if all rows satisfy the filter)
    • filterHardest

      @NotNull Forest filterHardest(@Nullable La<Long,?> filter)

      Filters this forest by excluding rows that do not pass the filter condition. All sub-rows of rows that have not passed the filter are also removed. The resulting forest contains only rows that pass the condition, but possibly not all of them.

      Unlike filter(com.almworks.jira.structure.api.util.La<java.lang.Long, ?>) method, this method preserves the topology of the original forest - all rows in the resulting forest have the same root path as they do in the original forest. However, unlike filterSoft(com.almworks.jira.structure.api.util.La<java.lang.Long, ?>) it achieves that by not including those matching rows that would change the hierarchy.

      This forest is not modified by this method. If all rows pass the condition, then this forest is returned as the result. If filtering has taken place, a new forest is returned.

      The filter method is called once for every row in the forest, and a truthy result (as defined in La.accepts(T)) indicates that the row passes the filter.

      Parameters:
      filter - filter that returns a truthy value if a row with given ID should be present in the resulting forest Null means "no filtering" - this forest is returned
      Returns:
      a filtered forest (or this forest if all rows satisfy the filter)
    • subtree

      @NotNull Forest subtree(long row)
      Creates a forest that contains the specified row and all its sub-rows from this forest.
      Parameters:
      row - the root of the sub-tree
      Returns:
      a new forest that contains a copy of the sub-tree rooted at row, or an empty forest if the row is not in this forest
    • subtreeAtIndex

      @NotNull Forest subtreeAtIndex(int index)
      Creates a forest that contains the specified row and all its sub-rows from this forest.
      Parameters:
      index - index of the root of the sub-tree
      Returns:
      a new forest that contains a copy of the sub-tree rooted at row at index, or an empty forest if the index is negative
    • copySubforest

      @Nullable ArrayForest copySubforest(long row)
    • copySubforestAtIndex

      @Nullable ArrayForest copySubforestAtIndex(int index)
    • copy

      ArrayForest copy()
      Creates an exact copy of this forest.
      Returns:
      a copy of this forest
    • visitParentChildrenUpwards

      void visitParentChildrenUpwards(ForestParentChildrenVisitor visitor)

      This method is used to efficiently traverse all pairs (parent, children) from the end of the forest upwards.

      This method goes over the forest in the backwards direction and reports to the visitor pairs of (parent, direct children).

      Invariants:

      • The number of calls to the visitor is equal to the forest size: every row is reported as a parent once.
      • A child row is reported (as the parent of its own sub-rows) before parent is reported: the iteration goes upwards.
      • A leaf row is reported as a parent with no children (empty children list).

      If the forest is modified during iteration, the results are undefined.

      Parameters:
      visitor - an instance that receives the pairs of parent and children array
    • foldUpwards

      @Nullable <T, C> C foldUpwards(ForestParentChildrenClosure<T,C> closure)

      This is a more generic version of visitParentChildrenUpwards(com.almworks.jira.structure.api.forest.raw.ForestParentChildrenVisitor) that allows you to effectively process the forest bottom-up, probably saving on memory allocation and search speed.

      The method goes over the forest in the backwards direction and calls closure methods for each row:

      Type Parameters:
      T - the type of the result of processing one row
      C - the type of the result of processing a number of sub-rows under the same parent
      Parameters:
      closure - the closure
      Returns:
      the result of processing top-level rows in the forest
      See Also:
    • scanDownwards

      void scanDownwards(ForestScanner scanner)
      Iterates through each row from top to the bottom. ForestScanner receives ForestScanControl, which can be used to cancel the scan, access parents, or skip subtrees.
      Parameters:
      scanner - the iteratee
    • toFullString

      @NotNull String toFullString()
      Utility method for debugging - returns full string representation of the forest, that contains all the information, unlike toString() method, which may be truncated to some character number limit.
      Returns:
      a full string containing all information about this forest