package org.basex.query.up;
import static org.basex.query.up.primitives.PrimitiveType.*;
import java.util.Arrays;
import org.basex.data.Data;
import org.basex.query.QueryException;
import org.basex.query.up.primitives.InsertBefore;
import org.basex.query.up.primitives.PrimitiveType;
import org.basex.query.up.primitives.StructuralUpdate;
import org.basex.query.up.primitives.UpdatePrimitive;
import org.basex.util.Util;
import org.basex.util.list.IntList;
import org.basex.util.list.ObjList;
/**
* This container holds all update primitives for a specific database node.
* It is identified by its target node's pre value and data reference. For each
* database node, which is target of an update primitive, a container like this
* accumulates all primitives that operate on this target.
*
* The pre value and data reference are not stored explicitly but are stored
* individually with each update primitive in this container.
*
*
* After updates in this container have been applied, there may be adjacent
* text nodes. Resolving this issue is divided into two steps:
*
* 1. Resolving internal text node adjacency:
* Can be done immediately after updates have been applied. The sibling
* axis of this containers target node is not affected. In addition updates
* on the descendant or following axis have already been carried out, as
* updates are applied database-wise from the highest to the lowest pre
* value.
* 2. Resolving external text node adjacency:
* Cannot be done on-the-fly as nodes on the preceding axis, which could
* also be target nodes, may be affected by merges.
*
* @author BaseX Team 2005-12, BSD License
* @author Lukas Kircher
*/
final class NodeUpdates {
/** Container for update primitives. In most cases there will only be a single
* update operation per target node, hence it's convenient to initialize this
* container with a size of one. */
UpdatePrimitive[] prim = new UpdatePrimitive[1];
/** The corresponding node is target of a delete primitive. */
private boolean del;
/** The corresponding node is target of a replace primitive. */
private boolean rep;
/** Is external text node adjacency possible as a result of the updates,
* that are held by this container. */
private boolean adjEXT;
/** Is internal text node adjacency possible. */
private boolean adjINT;
/**
* Adds an update primitive to this container.
* @param p primitive
* @throws QueryException query exception
*/
void add(final UpdatePrimitive p) throws QueryException {
// container is initialized with one empty space
if(prim[0] == null) {
prim[0] = p;
return;
}
/* If a primitive with the same type has already been added to
* the list, merge both primitives together. */
final PrimitiveType pt = p.type;
for(final UpdatePrimitive up : prim) {
if(up.type == pt) {
up.merge(p);
return;
}
}
// otherwise, add the given primitive to the end of the list
addToPrimitives(p);
}
/**
* Adds the given primitive to the end of the list.
* @param p Update primitive
*/
private void addToPrimitives(final UpdatePrimitive p) {
final int l = prim.length;
final UpdatePrimitive[] t = new UpdatePrimitive[l + 1];
System.arraycopy(prim, 0, t, 0, l);
t[l] = p;
prim = t;
}
/**
* Find the update primitive with the given type. In case there is no
* primitive of the given type, null is returned.
* @param t PrimitiveType
* @return primitive of type t, null if not found
*/
private UpdatePrimitive find(final PrimitiveType t) {
for(final UpdatePrimitive p : prim) if(p.type == t) return p;
return null;
}
/**
* Optimizes accumulated update operations for the specific target node
* and sorts update primitives in this container regarding their type
* {@link PrimitiveType}.
*
* Unnecessary operations are deleted. I.e. if the corresponding target is
* deleted, all other operations on this node have no effect at all.
*/
private void prepareExecution() {
for(final UpdatePrimitive p : prim) {
final PrimitiveType t = p.type;
del |= t == DELETENODE;
rep |= t == REPLACENODE;
}
/* Unnecessary primitives can only exist when the corresponding node is
* deleted or replaced.
*/
if(prim.length > 1 && (rep || del)) {
/* If a node is replaced, an eventual delete operation
is removed, as the actual node identity has been replaced. */
final PrimitiveType dominantOp = rep ? REPLACENODE : DELETENODE;
final ObjList<UpdatePrimitive> up = new ObjList<UpdatePrimitive>();
/*
* If a node is deleted or replaced, all other operations performing on
* the corresponding node identity are without effect in the end.
* Insert before/after form the only exception, as they do not affect
* the actual target node, but the sibling axis.
*/
for(final UpdatePrimitive p : prim) {
final PrimitiveType t = p.type;
if(t == INSERTBEFORE || t == INSERTAFTER || t == dominantOp) up.add(p);
}
prim = up.toArray(new UpdatePrimitive[up.size()]);
}
// determine if internal/external text node adjacency possible
for(final UpdatePrimitive p : prim) {
final PrimitiveType t = p.type;
del |= t == DELETENODE;
rep |= t == REPLACENODE;
adjEXT |= del || rep || t == INSERTBEFORE || t == INSERTAFTER;
adjINT |= t == INSERTINTO || t == INSERTINTOFIRST || t == INSERTINTOLAST;
}
/* Update primitives are executed in a strict order,
* see {@link PrimitiveType}. */
if(prim.length > 1) Arrays.sort(prim);
}
/**
* Returns whether the node identity with the given pre value is destroyed
* during the update process. A node identity can only be destroyed by a
* delete, a replace or replace element content expression.
* @param pre pre value of node to check
* @return node identity with given pre value is destroyed
*/
boolean updatesDestroyIdentity(final int pre) {
/* if this containers target node is destroyed we don't have to
* check for eventual replace element content expressions, which
* only affect the child axis. */
return (del || rep) && prim[0].pre == pre ||
destroyedNodeIdentities().contains(pre);
}
/**
* Calculates all pre values which identities are lost if all
* update primitives of this container are made effective. To
* determine node identities which are destroyed by a replace element
* content expression, we have to determine all child nodes of
* this target.
* @return pre values of destroyed node identities
*/
IntList destroyedNodeIdentities() {
final IntList d = new IntList();
for(final UpdatePrimitive p : prim) {
final PrimitiveType t = p.type;
switch(t) {
case DELETENODE:
case REPLACENODE:
d.add(p.pre);
break;
case REPLACEELEMCONT:
final Data data = p.data;
final int pre = p.pre;
final int kind = data.kind(pre);
final int l = pre + data.size(pre, kind);
int ipre = pre + data.attSize(pre, kind);
while(ipre < l) {
d.add(ipre);
ipre += data.size(ipre, data.kind(ipre));
}
break;
default:
}
}
return d;
}
/**
* Makes all primitives in this container effective and returns the total
* number of pre value shifts as a result of structural updates.
* @return number of pre value shifts
* @throws QueryException exception
*/
int makePrimitivesEffective() throws QueryException {
prepareExecution();
int sd = 0;
for(final UpdatePrimitive p : prim) {
p.apply();
if(p instanceof StructuralUpdate)
sd += ((StructuralUpdate) p).preShifts();
}
/* Internal text node adjacency is resolved and the number of pre value
* shifts introduced by this container altered accordingly.
*/
if(adjINT) return sd - resolveInternalTextNodeAdjacency();
return sd;
}
/**
* Resolves internal text node adjacency issues and returns the
* number of occasions where two text nodes have been merged into
* one to resolve these. This is necessary to take care of pre value
* shifts and to resolve external text node adjacency issues later.
* As we access this container at a later point again, we have to
* keep track of the target's pre value.
*
* Internal text node adjacency means, that an update primitive
* in this container may lead to text node adjacency on the target's
* child axis. We can resolve this issues immediately as changes
* won't affect the sibling axis of the target. If the sibling axis
* is affected (i.e. the target is deleted) we have to carry out all
* updates of the left sibling first (in case updates are held pending
* for the left sibling).
*
* Merges can only be applied after all updates on the child axis
* of this container's target node have been executed.
*
* @return number of text node merges
*/
private int resolveInternalTextNodeAdjacency() {
// number of occasions where text nodes have been merged
int merged = 0;
/* Second pass to merge potential adjacent text nodes.
* Applied backwards to account for pre value shifts
* which are kept track off by c.
*/
final InsertBefore ib = (InsertBefore) find(INSERTBEFORE);
int c = ib == null ? 0 : ib.preShifts();
for(int i = prim.length - 1; i >= 0; i--) {
if(prim[i] instanceof StructuralUpdate) {
final StructuralUpdate p = (StructuralUpdate) prim[i];
/* Skip primitives which have to be checked later (delete, replace,
* insert before) as they affect the sibling axis and primitives
* which cannot lead to adjacent text nodes.
*/
if(p.type != INSERTAFTER && p.type != INSERTBEFORE &&
p.type != REPLACENODE && p.type != DELETENODE) {
if(p.adjacentTexts(c)) {
merged++;
c--;
}
c += p.preShifts();
}
}
}
return merged;
}
/**
* Resolves external text node adjacency issues. As this is the last time
* we touch this specific container we don't have to keep track of pre
* value shifts any longer, so there's no return value.
*
* External text node adjacency can occur if an update primitive in this
* container affects the sibling axis of this target node (Delete, Replace,
* Insert After, Insert Before, see {@link PrimitiveType}.
*
* @param preShifts amount of pre value shifts that affect this container's
* target pre value. These shifts are a product of the next container on
* the preceding sibling axis. The 'left sibling container'.
*/
void resolveExternalTextNodeAdjacency(final int preShifts) {
if(!adjEXT) return;
// take pre value shifts into account
int c = preShifts;
/* Check for adjacency backwards to account for pre value shifts introduced
* by the individual updates in this container -> from the lowest to the
* highest pre value
*/
for(int i = prim.length - 1; i >= 0; i--) {
if(prim[i] instanceof StructuralUpdate) {
final StructuralUpdate p = (StructuralUpdate) prim[i];
// only check primitives which can lead to external text node adjacency
if(p.type == INSERTAFTER || p.type == INSERTBEFORE ||
p.type == DELETENODE || p.type == REPLACENODE) {
if(p.adjacentTexts(c))
c--;
}
// consider amount of nodes deleted/inserted by the current primitive
c += p.preShifts();
}
}
}
@Override
public String toString() {
return Util.name(this);
}
}