/** * Calculates the PRE value of a given node before/after updates. * * <p>Finds all updates that affect the given node N. The result is than calculated based on N and * the accumulated PRE value shifts introduced by these updates. * * <p>If a node has been inserted at position X and this method is used to calculate the PRE value * of X before updates, X is the result. As the node at position X has not existed before the * insertion, its PRE value is unchanged. If in contrast the PRE value is calculated after * updates, the result is X+1, as the node with the original position X has been shifted by the * insertion at position X. * * <p>Make sure accumulated shifts have been calculated before calling this method! * * @param pre PRE value * @param beforeUpdates calculate PRE value before shifts/updates have been applied * @return index of update, or -1 */ public int calculatePreValue(final int pre, final boolean beforeUpdates) { int i = find(pre, beforeUpdates); // given PRE not changed by updates if (i == -1) return pre; i = refine(updStructural, i, beforeUpdates); final int acm = updStructural.get(i).accumulatedShifts; return beforeUpdates ? pre - acm : pre + acm; }
/** 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; }
/** * 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; }
/** * 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(); }
/** * Recalculates the PRE value of the first node whose distance is affected by the given update. * * @param l list of updates * @param index index of the update * @param beforeUpdates calculate PRE value before or after updates * @return PRE value */ private int c(final List<BasicUpdate> l, final int index, final boolean beforeUpdates) { final BasicUpdate u = l.get(index); return u.preOfAffectedNode + (beforeUpdates ? u.accumulatedShifts : 0); }