/** Calculates the accumulated PRE value shifts for all updates on the list. */
 private void accumulatePreValueShifts() {
   if (!dirty) return;
   int s = 0;
   for (int i = updStructural.size() - 1; i >= 0; i--) {
     final BasicUpdate t = updStructural.get(i);
     s += t.shifts;
     t.accumulatedShifts = s;
   }
   dirty = false;
 }
  /**
   * Checks the list of updates for violations. Updates must be ordered strictly from the highest to
   * the lowest PRE value.
   *
   * <p>A single node must not be affected by more than one {@link Rename}, {@link UpdateValue}
   * operation.
   *
   * <p>A single node must not be affected by more than one destructive operation. These operations
   * include {@link Replace}, {@link Delete}.
   */
  public void check() {
    if (ok || updStructural.size() < 2 && updValue.size() < 2) return;

    int i = 0;
    while (i + 1 < updStructural.size()) {
      final BasicUpdate current = updStructural.get(i);
      final BasicUpdate next = updStructural.get(++i);

      // check order of location PRE
      if (current.location < next.location)
        Util.notexpected("Invalid order at location " + current.location);

      if (current.location == next.location) {
        // check multiple {@link Delete}, {@link Replace}
        if (current.destructive() && next.destructive())
          Util.notexpected("Multiple deletes/replaces on node " + current.location);
      }
    }

    i = 0;
    while (i + 1 < updValue.size()) {
      final BasicUpdate current = updValue.get(i++);
      final BasicUpdate next = updValue.get(i);

      // check order of location PRE
      if (current.location < next.location)
        Util.notexpected("Invalid order at location " + current.location);

      if (current.location == next.location) {
        // check multiple {@link Rename}
        if (current instanceof Rename && next instanceof Rename)
          Util.notexpected("Multiple renames on node " + current.location);

        // check multiple {@link UpdateValue}
        if (current instanceof UpdateValue && next instanceof UpdateValue)
          Util.notexpected("Multiple updates on node " + current.location);
      }
    }
    ok = true;
  }
  /**
   * Resolves unwanted text node adjacency which can result from structural changes in the database.
   * Adjacent text nodes are two text nodes A and B, where PRE(B)=PRE(A)+1 and PARENT(A)=PARENT(B).
   */
  private void resolveTextAdjacency() {
    // Text node merges are also gathered on a separate list to leverage optimizations.
    final AtomicUpdateList allMerges = new AtomicUpdateList(data);

    // keep track of the visited locations to avoid superfluous checks
    final IntSet s = new IntSet();
    // Text nodes have to be merged from the highest to the lowest pre value
    for (int i = 0; i < updStructural.size(); i++) {
      final BasicUpdate u = updStructural.get(i);
      final Data insseq = u.getInsertionData();
      // calculate the new location of the update, here we have to check for adjacency
      final int newLocation = u.location + u.accumulatedShifts - u.shifts;
      final int beforeNewLocation = newLocation - 1;
      // check surroundings of this location for adjacent text nodes depending on the
      // kind of update, first the one with higher PRE values (due to shifts!)
      // ... for insert/replace ...
      if (insseq != null) {
        // calculate the current following node
        final int followingNode = newLocation + insseq.meta.size;
        final int beforeFollowingNode = followingNode - 1;
        // check the nodes at the end of/after the insertion sequence
        if (!s.contains(beforeFollowingNode)) {
          final AtomicUpdateList merges = necessaryMerges(beforeFollowingNode, allMerges.data);
          mergeNodes(merges);
          allMerges.merge(merges);
          s.add(beforeFollowingNode);
        }
      }
      // check nodes for delete and for insert before the updated location
      if (!s.contains(beforeNewLocation)) {
        final AtomicUpdateList merges = necessaryMerges(beforeNewLocation, allMerges.data);
        mergeNodes(merges);
        allMerges.merge(merges);
        s.add(beforeNewLocation);
      }
    }

    allMerges.updateDistances();
    allMerges.clear();
  }
  /**
   * Removes superfluous update operations. If a node T is deleted or replaced, all updates on the
   * descendant axis of T can be left out as they won't affect the database after all.
   *
   * <p>Superfluous updates can have a minimum PRE value of pre(T)+1 and a maximum PRE value of
   * pre(T)+size(T).
   *
   * <p>An update with location pre(T)+size(T) can only be removed if the update is an atomic insert
   * and the inserted node is then part of the subtree of T.
   */
  public void optimize() {
    if (opt) return;

    check();
    // traverse from lowest to highest PRE value
    int i = updStructural.size() - 1;
    while (i >= 0) {
      final BasicUpdate u = updStructural.get(i);
      // If this update can lead to superfluous updates ...
      if (u.destructive()) {
        // we determine the lowest and highest PRE values of a superfluous update
        final int pre = u.location;
        final int fol = pre + data.size(pre, data.kind(pre));
        i--;
        // and have a look at the next candidate
        while (i >= 0) {
          final BasicUpdate desc = updStructural.get(i);
          final int descpre = desc.location;
          // if the candidate operates on the subtree of T and inserts a node ...
          if (descpre <= fol
              && (desc instanceof Insert || desc instanceof InsertAttr)
              && desc.parent() >= pre
              && desc.parent() < fol) {
            // it is removed.
            updStructural.remove(i--);

            // Other updates (not inserting a node) that operate on the subtree of T can
            // only have a PRE value that is smaller than the following PRE of T
          } else if (descpre < fol) {
            // these we delete.
            updStructural.remove(i--);

            // Else there's nothing to delete
          } else break;
        }
      } else i--;
    }
    opt = true;
  }
  /**
   * Finds the position of the update with the lowest index that affects the given PRE value P. If
   * there are multiple updates whose affected PRE value equals P, the search has to be further
   * refined as this method returns the first match.
   *
   * @param pre given PRE value
   * @param beforeUpdates compare based on PRE values before/after updates
   * @return index of update
   */
  private int find(final int pre, final boolean beforeUpdates) {
    int left = 0;
    int right = updStructural.size() - 1;

    while (left <= right) {
      if (left == right) {
        if (c(updStructural, left, beforeUpdates) <= pre) return left;
        return -1;
      }
      if (right - left == 1) {
        if (c(updStructural, left, beforeUpdates) <= pre) return left;
        if (c(updStructural, right, beforeUpdates) <= pre) return right;
        return -1;
      }
      final int middle = left + right >>> 1;
      final int value = c(updStructural, middle, beforeUpdates);
      if (value == pre) return middle;
      else if (value > pre) left = middle + 1;
      else right = middle;
    }

    // empty array
    return -1;
  }
 /**
  * Returns the number of value updates.
  *
  * @return number of value updates
  */
 public int valueUpdatesSize() {
   return updValue.size();
 }
 /**
  * Returns the number of structural updates.
  *
  * @return number of structural updates
  */
 public int structuralUpdatesSize() {
   return updStructural.size();
 }