package org.basex.data;
import static org.basex.util.Token.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.basex.core.cmd.InfoStorage;
import org.basex.index.IdPreMap;
import org.basex.index.Index;
import org.basex.index.IndexIterator;
import org.basex.index.IndexToken;
import org.basex.index.IndexToken.IndexType;
import org.basex.index.Names;
import org.basex.index.Resources;
import org.basex.index.path.PathSummary;
import org.basex.io.IO;
import org.basex.io.random.TableAccess;
import org.basex.util.Atts;
import org.basex.util.TokenBuilder;
import org.basex.util.Util;
import org.basex.util.hash.TokenMap;
import org.basex.util.list.IntList;
/**
* This class provides access to the database storage.
* Note that the methods of this class are optimized for performance.
* They will not check if correct data is requested, i.e. if a text is
* requested, a pre value must points to a text node.
*
* All nodes in the table are accessed by their
* implicit pre value. The following restrictions are imposed on the data:
* <ul>
* <li>The table is limited to 2^31 entries (pre values are signed int's)</li>
* <li>A maximum of 2^15 different tag and attribute names is allowed</li>
* <li>A maximum of 2^8 different namespaces is allowed</li>
* <li>A tag can have a maximum of 32 attributes</li>
* </ul>
* Each node occupies 128 bits. The current storage layout looks as follows:
*
* <pre>
* COMMON ATTRIBUTES:
* - Byte 0: KIND: Node kind (bits: 2-0)
* - Byte 12-15: UNID: Unique Node ID
* DOCUMENT NODES (kind = 0):
* - Byte 3- 7: TEXT: Text reference
* - Byte 8-11: SIZE: Number of descendants
* ELEMENT NODES (kind = 1):
* - Byte 0: ATTS: Number of attributes (bits: 7-3).
* Calculated in real-time, if bit range is too small
* - Byte 1- 2: NAME: Namespace Flag (bit: 15), Name (bits: 14-0)
* - Byte 3: NURI: Namespace URI
* - Byte 4- 7: DIST: Distance to parent node
* - Byte 8-11: SIZE: Number of descendants
* TEXT, COMMENT, PI NODES (kind = 2, 4, 5):
* - Byte 3- 7: TEXT: Text reference
* - Byte 8-11: DIST: Distance to parent node
* ATTRIBUTE NODES (kind = 3):
* - Byte 0: DIST: Distance to parent node (bits: 7-3)
* Calculated in real-time, if bit range is too small
* - Byte 1- 2: NAME: Namespace Flag (bit: 15), Name (bits: 14-0)
* - Byte 3- 7: TEXT: Attribute value reference
* - Byte 11: NURI: Namespace (bits: 7-3)
* </pre>
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public abstract class Data {
/** Node kind: Document. */
public static final byte DOC = 0x00;
/** Node kind: Element. */
public static final byte ELEM = 0x01;
/** Node kind: Text. */
public static final byte TEXT = 0x02;
/** Node kind: Attribute. */
public static final byte ATTR = 0x03;
/** Node kind: Comment. */
public static final byte COMM = 0x04;
/** Node kind: Processing Instruction. */
public static final byte PI = 0x05;
/** Meta data. */
public MetaData meta;
/** Tag index. */
public Names tagindex;
/** Attribute name index. */
public Names atnindex;
/** Namespace index. */
public Namespaces nspaces;
/** Path summary index. */
public PathSummary paths;
/** Resource index. */
public final Resources resources = new Resources(this);
/** Text index. */
public Index txtindex;
/** Attribute value index. */
public Index atvindex;
/** Full-text index instance. */
public Index ftxindex;
/** Index reference for a name attribute. */
public int nameID;
/** Index reference for a size attribute. */
public int sizeID;
/** Table access file. */
TableAccess table;
/** ID->PRE mapping. */
IdPreMap idmap;
/**
* Dissolves the references to often used tag names and attributes.
* @throws IOException I/O exception
*/
@SuppressWarnings("unused")
public void init() throws IOException {
nameID = atnindex.id(DataText.T_NAME);
sizeID = atnindex.id(DataText.T_SIZE);
}
/**
* Closes the current database.
* @throws IOException I/O exception
*/
public abstract void close() throws IOException;
/**
* Flushes the database.
*/
public abstract void flush();
/**
* Checks if the database contains no documents.
* Empty databases can be recognized by a single document node.
* @return result of check
*/
public final boolean empty() {
return meta.size == 1 && kind(0) == DOC;
}
/**
* Checks if the database contains a single document.
* @return result of check
*/
public final boolean single() {
return meta.size == size(0, DOC);
}
/**
* Closes the specified index.
* @param type index to be closed
* @throws IOException I/O exception
*/
public abstract void closeIndex(IndexType type) throws IOException;
/**
* Assigns the specified index.
* @param type index to be opened
* @param index index instance
*/
public abstract void setIndex(IndexType type, Index index);
/**
* Marks a database as updating.
* @param updating updating flag
* @return result flag
*/
public abstract boolean updating(final boolean updating);
/**
* Returns the indexed pre references for the specified token.
* @param token index token reference
* @return array of sorted pre values
*/
public final IndexIterator iter(final IndexToken token) {
return index(token.type()).iter(token);
}
/**
* Returns the number of indexed pre references for the specified token.
* @param token text to be found
* @return number of hits
*/
public final int count(final IndexToken token) {
return index(token.type()).count(token);
}
/**
* Returns info on the specified index structure.
* @param type index type
* @return info
*/
public final byte[] info(final IndexType type) {
return index(type).info();
}
/**
* Returns the index reference for the specified index type.
* @param type index type
* @return index
*/
final Index index(final IndexType type) {
switch(type) {
case TAG: return tagindex;
case ATTNAME: return atnindex;
case TEXT: return txtindex;
case ATTRIBUTE: return atvindex;
case FULLTEXT: return ftxindex;
case PATH: return paths;
default: throw Util.notexpected();
}
}
/**
* Returns an atomized content for any node kind.
* The atomized value can be an attribute value or XML content.
* @param pre pre value
* @return atomized value
*/
public final byte[] atom(final int pre) {
switch(kind(pre)) {
case TEXT: case COMM:
return text(pre, true);
case ATTR:
return text(pre, false);
case PI:
byte[] txt = text(pre, true);
final int i = indexOf(txt, ' ');
return i == -1 ? EMPTY : substring(txt, i + 1);
default:
// create atomized text node
TokenBuilder tb = null;
byte[] t = EMPTY;
int p = pre;
final int s = p + size(p, kind(p));
while(p != s) {
final int k = kind(p);
if(k == TEXT) {
txt = text(p, true);
if(t == EMPTY) {
t = txt;
} else {
if(tb == null) tb = new TokenBuilder(t);
tb.add(txt);
}
}
p += attSize(p, k);
}
return tb == null ? t : tb.finish();
}
}
// RETRIEVING VALUES ========================================================
/**
* Returns a pre value.
* @param id unique node id
* @return pre value or -1 if id was not found
*/
final int preold(final int id) {
// find pre value in table
for(int p = Math.max(0, id); p < meta.size; ++p)
if(id == id(p)) return p;
for(int p = 0, ps = Math.min(meta.size, id); p < ps; ++p)
if(id == id(p)) return p;
// id not found
return -1;
}
/**
* Returns a pre value.
* @param id unique node id
* @return pre value or -1 if id was not found
*/
public final int pre(final int id) {
if(meta.updindex) return idmap.pre(id);
return preold(id);
}
/**
* Returns pre values.
* @param ids unique node ids
* @param off start offset
* @param len number of ids
* @return sorted pre values
*/
public final int[] pre(final int[] ids, final int off, final int len) {
if(meta.updindex) return idmap.pre(ids, off, len);
final IntList p = new IntList(ids.length);
for(int i = off; i < len; ++i) p.add(preold(ids[i]));
return p.sort().toArray();
}
/**
* Returns a unique node id.
* @param pre pre value
* @return node id
*/
public final int id(final int pre) {
return table.read4(pre, 12);
}
/**
* Returns a node kind.
* @param pre pre value
* @return node kind
*/
public final int kind(final int pre) {
return table.read1(pre, 0) & 0x07;
}
/**
* Returns a pre value of the parent node.
* @param pre pre value
* @param kind node kind
* @return pre value of the parent node
*/
public final int parent(final int pre, final int kind) {
return pre - dist(pre, kind);
}
/**
* Returns the distance of the specified node.
* @param pre pre value
* @param kind node kind
* @return distance
*/
private int dist(final int pre, final int kind) {
switch(kind) {
case ELEM:
return table.read4(pre, 4);
case TEXT:
case COMM:
case PI:
return table.read4(pre, 8);
case ATTR:
int d = table.read1(pre, 0) >> 3 & IO.MAXATTS;
// skip additional attributes, if value is larger than maximum range
if(d >= IO.MAXATTS) while(d < pre && kind(pre - d) == ATTR) d++;
return d;
default:
return pre + 1;
}
}
/**
* Returns a size value (number of descendant table entries).
* @param pre pre value
* @param kind node kind
* @return size value
*/
public final int size(final int pre, final int kind) {
return kind == ELEM || kind == DOC ? table.read4(pre, 8) : 1;
}
/**
* Returns a number of attributes.
* @param pre pre value
* @param kind node kind
* @return number of attributes
*/
public final int attSize(final int pre, final int kind) {
int s = kind == ELEM ? table.read1(pre, 0) >> 3 & IO.MAXATTS : 1;
// skip additional attributes, if value is larger than maximum range
if(s >= IO.MAXATTS) while(s < meta.size - pre && kind(pre + s) == ATTR) s++;
return s;
}
/**
* Finds the specified attribute and returns its value.
* @param att the attribute id of the attribute to be found
* @param pre pre value
* @return attribute value
*/
public final byte[] attValue(final int att, final int pre) {
final int a = pre + attSize(pre, kind(pre));
int p = pre;
while(++p != a) if(name(p) == att) return text(p, false);
return null;
}
/**
* Returns a reference to the tag or attribute name id.
* @param pre pre value
* @return token reference
*/
public final int name(final int pre) {
return table.read2(pre, 1) & 0x7FFF;
}
/**
* Returns a tag, attribute or pi name.
* @param pre pre value
* @param kind node kind
* @return name reference
*/
public final byte[] name(final int pre, final int kind) {
if(kind == PI) {
final byte[] name = text(pre, true);
final int i = indexOf(name, ' ');
return i == -1 ? name : substring(name, 0, i);
}
return (kind == ELEM ? tagindex : atnindex).key(name(pre));
}
/**
* Returns a reference to the namespace of the addressed element or attribute.
* @param pre pre value
* @param kind node kind
* @return namespace URI
*/
public final int uri(final int pre, final int kind) {
return kind == ELEM || kind == ATTR ?
table.read1(pre, kind == ELEM ? 3 : 11) & 0xFF : 0;
}
/**
* Returns the namespace flag of the addressed element.
* @param pre pre value
* @return namespace flag
*/
public final boolean nsFlag(final int pre) {
return (table.read1(pre, 1) & 0x80) != 0;
}
/**
* Returns all namespace keys and values.
* Should be only called for element nodes.
* @param pre pre value
* @return key and value ids
*/
public final Atts ns(final int pre) {
final Atts as = new Atts();
if(nsFlag(pre)) {
final int[] nsp = nspaces.get(pre);
for(int n = 0; n < nsp.length; n += 2)
as.add(nspaces.prefix(nsp[n]), nspaces.uri(nsp[n + 1]));
}
return as;
}
/**
* Returns the disk offset of a text (text, comment, pi) or attribute value.
* @param pre pre value
* @return disk offset
*/
final long textOff(final int pre) {
return table.read5(pre, 3);
}
/**
* Returns a text (text, comment, pi) or attribute value.
* @param pre pre value
* @param text text/attribute flag
* @return atomized value
*/
public abstract byte[] text(int pre, boolean text);
/**
* Returns a text (text, comment, pi) or attribute value as integer value.
* @param pre pre value
* @param text text/attribute flag
* @return numeric value
*/
public abstract long textItr(int pre, boolean text);
/**
* Returns a text (text, comment, pi) or attribute value as double value.
* @param pre pre value
* @param text text/attribute flag
* @return numeric value
*/
public abstract double textDbl(int pre, boolean text);
/**
* Returns the byte length of a text (text, comment, pi).
* @param pre pre value
* @param text text/attribute flag
* @return length
*/
public abstract int textLen(int pre, boolean text);
// UPDATE OPERATIONS ========================================================
/**
* Updates (renames) an element, attribute or pi name.
* @param pre pre value
* @param kind node kind
* @param name new tag, attribute or pi name
* @param uri uri
*/
public final void update(final int pre, final int kind, final byte[] name,
final byte[] uri) {
meta.update();
if(kind == PI) {
updateText(pre, trim(concat(name, SPACE, atom(pre))), kind);
} else {
// update/set namespace reference
final int ouri = nspaces.uri(name, pre);
final boolean ne = ouri == 0 && uri.length != 0;
final int npre = kind == ATTR ? parent(pre, kind) : pre;
final int nuri = ne ? nspaces.add(npre, npre, prefix(name), uri) :
ouri != 0 && eq(nspaces.uri(ouri), uri) ? ouri : 0;
// write namespace uri reference
table.write1(pre, kind == ELEM ? 3 : 11, nuri);
// write name reference
table.write2(pre, 1, (nsFlag(pre) ? 1 << 15 : 0) |
(kind == ELEM ? tagindex : atnindex).index(name, null, false));
// write namespace flag
table.write2(npre, 1, (ne || nsFlag(npre) ? 1 << 15 : 0) | name(npre));
}
}
/**
* Updates (replaces) the value of a single text, comment, pi or
* attribute node.
* @param pre pre value to be replaced
* @param kind node kind
* @param value value to be updated (tag name, text, comment, pi)
*/
public final void update(final int pre, final int kind, final byte[] value) {
meta.update();
updateText(pre, kind == PI ? trim(concat(name(pre, kind), SPACE, value)) :
value, kind);
if(kind == DOC) resources.rename(pre, value);
}
/**
* Replaces parts of the database with the specified data instance.
* @param rpre pre value to be replaced
* @param data replace data
*/
public final void replace(final int rpre, final Data data) {
meta.update();
final int dsize = data.meta.size;
final int rkind = kind(rpre);
final int rsize = size(rpre, rkind);
final int rpar = parent(rpre, rkind);
final int diff = dsize - rsize;
buffer(dsize);
resources.replace(rpre, rsize, data);
if(meta.updindex) {
// update index
indexDelete(rpre, rsize);
indexBegin();
}
for(int dpre = 0; dpre < dsize; dpre++) {
final int dkind = data.kind(dpre);
final int dpar = data.parent(dpre, dkind);
final int pre = rpre + dpre;
final int dis = dpar >= 0 ? dpre - dpar : pre - parent(rpre, rkind);
switch(dkind) {
case DOC:
// add document
doc(pre, data.size(dpre, dkind), data.text(dpre, true));
meta.ndocs++;
break;
case ELEM:
// add element
byte[] nm = data.name(dpre, dkind);
elem(dis, tagindex.index(nm, null, false), data.attSize(dpre, dkind),
data.size(dpre, dkind), nspaces.uri(nm, true), false);
break;
case TEXT:
case COMM:
case PI:
// add text
text(pre, dis, data.text(dpre, true), dkind);
break;
case ATTR:
// add attribute
nm = data.name(dpre, dkind);
attr(pre, dis, atnindex.index(nm, null, false),
data.text(dpre, false), nspaces.uri(nm, false), false);
break;
}
}
if(meta.updindex) {
indexEnd();
// update ID -> PRE map:
idmap.delete(rpre, id(rpre), -rsize);
idmap.insert(rpre, meta.lastid - dsize + 1, dsize);
}
// update table:
table.replace(rpre, buffer(), rsize);
buffer(1);
// no distance/size update if the two subtrees are of equal size
if(diff == 0) return;
// increase/decrease size of ancestors, adjust distances of siblings
int p = rpar;
while(p >= 0) {
final int k = kind(p);
size(p, k, size(p, k) + diff);
p = parent(p, k);
}
updateDist(rpre + dsize, diff);
// adjust attribute size of parent if attributes inserted. attribute size
// of parent cannot be reduced via a replace expression.
if(data.kind(0) == ATTR) {
int d = 0, i = 0;
while(i < dsize && data.kind(i++) == ATTR) d++;
if(d > 1) attSize(rpar, kind(rpar), d + 1);
}
}
/**
* Deletes a node and its descendants.
* @param pre pre value of the node to delete
*/
public final void delete(final int pre) {
meta.update();
// size of the subtree to delete
int k = kind(pre);
int s = size(pre, k);
// indicates if database is empty
final boolean empty = pre == 0 && s == meta.size;
// update document index: delete specified entry
if(!empty) resources.delete(pre, s);
if(meta.updindex) {
// delete child records from indexes
indexDelete(pre, s);
}
/// explicitly delete text or attribute value
if(k != DOC && k != ELEM) delete(pre, k != ATTR);
// update namespaces
nspaces.delete(pre, s);
// reduce size of ancestors
int par = pre;
// check if we are an attribute (different size counters)
if(k == ATTR) {
par = parent(par, ATTR);
attSize(par, ELEM, attSize(par, ELEM) - 1);
size(par, ELEM, size(par, ELEM) - 1);
k = kind(par);
}
// reduce size of ancestors
while(par > 0 && k != DOC) {
par = parent(par, k);
k = kind(par);
size(par, k, size(par, k) - s);
}
// preserve empty root node
int p = pre;
if(empty) {
++p;
--s;
} else if(kind(p) == DOC) {
--meta.ndocs;
}
if(meta.updindex) {
// delete node and descendants from ID -> PRE map:
idmap.delete(pre, id(pre), -s);
}
// delete node from table structure and reduce document size
table.delete(pre, s);
updateDist(p, -s);
// NSNodes have to be checked for pre value shifts after delete
nspaces.update(pre, s, false, null);
// restore empty document node
if(empty) {
doc(pre, 1, EMPTY);
table.set(0, buffer());
if(meta.updindex) idmap.insert(0, id(0), 1);
}
}
/**
* Inserts attributes.
* @param pre pre value
* @param par parent of node
* @param data data instance to copy from
*/
public final void insertAttr(final int pre, final int par, final Data data) {
insert(pre, par, data);
attSize(par, ELEM, attSize(par, ELEM) + data.meta.size);
}
/**
* Inserts a data instance at the specified pre value.
* Note that the specified data instance must differ from this instance.
* @param ipre value at which to insert new data
* @param ipar parent pre value of node
* @param data data instance to copy from
*/
public final void insert(final int ipre, final int ipar, final Data data) {
meta.update();
// update value and document indexes
if(meta.updindex) indexBegin();
resources.insert(ipre, data);
// indicates if database only contains a dummy node
final boolean dummy = empty() && data.kind(0) == DOC;
final int dsize = data.meta.size;
final int buf = Math.min(dsize, IO.BLOCKSIZE >> IO.NODEPOWER);
// resize buffer to cache more entries
buffer(buf);
// find all namespaces in scope to avoid duplicate declarations
final TokenMap nsScope = new TokenMap();
NSNode n = nspaces.current;
do {
for(int i = 0; i < n.vals.length; i += 2)
nsScope.add(nspaces.prefix(n.vals[i]), nspaces.uri(n.vals[i + 1]));
final int pos = n.fnd(ipar);
if(pos < 0) break;
n = n.ch[pos];
} while(n.pre <= ipar && ipar < n.pre + size(n.pre, ELEM));
// loop through all entries
final IntList preStack = new IntList();
int dpre = -1;
final NSNode t = nspaces.current;
final Set<NSNode> newNodes = new HashSet<NSNode>();
final IntList flagPres = new IntList();
while(++dpre != dsize) {
if(dpre != 0 && dpre % buf == 0) insert(ipre + dpre - buf);
final int pre = ipre + dpre;
final int dkind = data.kind(dpre);
final int dpar = data.parent(dpre, dkind);
final int dis = dpar >= 0 ? dpre - dpar : ipar >= 0 ? pre - ipar : 0;
final int par = dis == 0 ? -1 : pre - dis;
// find nearest namespace node on the ancestor axis of the insert
// location. possible candidates for this node are collected and
// the match with the highest pre value between ancestors and candidates
// is determined.
if(dpre == 0) {
// collect possible candidates for namespace root
final List<NSNode> cand = new LinkedList<NSNode>();
NSNode cn = nspaces.root;
cand.add(cn);
for(int cI; (cI = cn.fnd(par)) > -1;) {
// add candidate to stack
cn = cn.ch[cI];
cand.add(0, cn);
}
cn = nspaces.root;
if(cand.size() > 1) {
// compare candidates to ancestors of par
int ancPre = par;
// take first candidate from stack
NSNode curr = cand.remove(0);
while(ancPre > -1 && cn == nspaces.root) {
// this is the new root
if(curr.pre == ancPre) cn = curr;
// if the current candidate's pre value is lower than the current
// ancestor of par or par itself we have to look for a potential
// match for this candidate. therefore we iterate through ancestors
// till we find one with a lower than or the same pre value as the
// current candidate.
else if(curr.pre < ancPre) {
while((ancPre = parent(ancPre, kind(ancPre))) > curr.pre);
if(curr.pre == ancPre) cn = curr;
}
// no potential for infinite loop, cause dummy root always a match,
// in this case ancPre ends iteration
if(!cand.isEmpty()) curr = cand.remove(0);
}
}
nspaces.setNearestRoot(cn, par);
}
while(preStack.size() != 0 && preStack.peek() > par)
nspaces.close(preStack.pop());
switch(dkind) {
case DOC:
// add document
final int s = data.size(dpre, dkind);
doc(pre, s, data.text(dpre, true));
meta.ndocs++;
nspaces.open();
preStack.push(pre);
break;
case ELEM:
// add element
boolean ne = false;
if(data.nsFlag(dpre)) {
final Atts at = data.ns(dpre);
for(int a = 0; a < at.size(); ++a) {
// see if prefix has been declared/ is part of current ns scope
final byte[] old = nsScope.get(at.name(a));
if(old == null || !eq(old, at.string(a))) {
// we have to keep track of all new NSNodes that are added
// to the Namespace structure, as their pre values must not
// be updated. I.e. if an NSNode N with pre value 3 existed
// prior to inserting and two new nodes are inserted at
// location pre == 3 we have to make sure N and only N gets
// updated.
newNodes.add(nspaces.add(at.name(a), at.string(a), pre));
ne = true;
}
}
}
nspaces.open();
byte[] nm = data.name(dpre, dkind);
elem(dis, tagindex.index(nm, null, false), data.attSize(dpre, dkind),
data.size(dpre, dkind), nspaces.uri(nm, true), ne);
preStack.push(pre);
break;
case TEXT:
case COMM:
case PI:
// add text
text(pre, dis, data.text(dpre, true), dkind);
break;
case ATTR:
// add attribute
nm = data.name(dpre, dkind);
// check if prefix already in nsScope or not
final byte[] attPref = prefix(nm);
// check if prefix of attribute has already been declared, otherwise
// add declaration to parent node
if(data.nsFlag(dpre) && nsScope.get(attPref) == null) {
nspaces.add(par, preStack.size() == 0 ? -1 : preStack.peek(),
attPref, data.nspaces.uri(data.uri(dpre, dkind)));
// save pre value to set ns flag later for this node. can't be done
// here as direct table access would interfere with the buffer
flagPres.add(par);
}
attr(pre, dis, atnindex.index(nm, null, false),
data.text(dpre, false), nspaces.uri(nm, false), false);
break;
}
}
while(preStack.size() != 0) nspaces.close(preStack.pop());
nspaces.setRoot(t);
if(bp != 0) insert(ipre + dpre - 1 - (dpre - 1) % buf);
// reset buffer to old size
buffer(1);
// set ns flags
for(final int toFlag : flagPres.toArray())
table.write2(toFlag, 1, name(toFlag) | 1 << 15);
// increase size of ancestors
int p = ipar;
while(p >= 0) {
final int k = kind(p);
size(p, k, size(p, k) + dsize);
p = parent(p, k);
}
updateDist(ipre + dsize, dsize);
// NSNodes have to be checked for pre value shifts after insert
nspaces.update(ipre, dsize, true, newNodes);
if(meta.updindex) {
// add the entries to the ID -> PRE mapping:
idmap.insert(ipre, id(ipre), dsize);
indexEnd();
}
// delete old empty root node
if(dummy) delete(0);
}
/**
* This method updates the distance values of the specified pre value
* and the following siblings of all ancestor-or-self nodes.
* @param pre root node
* @param size size to be added/removed
*/
private void updateDist(final int pre, final int size) {
int p = pre;
while(p < meta.size) {
final int k = kind(p);
dist(p, k, dist(p, k) + size);
p += size(p, k);
}
}
/**
* Sets the size value.
* @param pre pre reference
* @param kind node kind
* @param value value to be stored
*/
public final void size(final int pre, final int kind, final int value) {
if(kind == ELEM || kind == DOC) table.write4(pre, 8, value);
}
/**
* Sets the disk offset of a text/attribute value.
* @param pre pre value
* @param off offset
*/
final void textOff(final int pre, final long off) {
table.write5(pre, 3, off);
}
/**
* Updates the specified text or attribute value.
* @param pre pre value
* @param value content
* @param kind node kind
*/
protected abstract void updateText(final int pre, final byte[] value,
final int kind);
/**
* Sets the distance.
* @param pre pre value
* @param kind node kind
* @param value value
*/
private void dist(final int pre, final int kind, final int value) {
if(kind == ATTR) table.write1(pre, 0, value << 3 | ATTR);
else if(kind != DOC) table.write4(pre, kind == ELEM ? 4 : 8, value);
}
/**
* Sets the attribute size.
* @param pre pre value
* @param kind node kind
* @param value value
*/
private void attSize(final int pre, final int kind, final int value) {
if(kind == ELEM) table.write1(pre, 0, value << 3 | ELEM);
}
/**
* Sets the namespace flag.
* Should be only called for element nodes.
* @param pre pre value
* @param ne namespace flag
*/
public final void nsFlag(final int pre, final boolean ne) {
table.write1(pre, 1, table.read1(pre, 1) & 0x7F | (ne ? 0x80 : 0));
}
/**
* Inserts the internal buffer to the storage
* without updating the table structure.
* @param pre insert position
*/
public final void insert(final int pre) {
table.insert(pre, buffer());
}
/**
* Deletes the specified text entry.
* @param pre pre value
* @param text text (text, comment or pi) or attribute flag
*/
protected abstract void delete(final int pre, final boolean text);
// INSERTS WITHOUT TABLE UPDATES ============================================
/** Buffer for caching new table entries. */
private byte[] b = new byte[IO.NODESIZE];
/** Buffer position. */
private int bp;
/**
* Sets the update buffer to a new size.
* @param size number of table entries
*/
final void buffer(final int size) {
final int bs = size << IO.NODEPOWER;
if(b.length != bs) b = new byte[bs];
}
/**
* Adds a document entry to the internal update buffer.
* @param pre pre value
* @param size node size
* @param value document name
*/
public final void doc(final int pre, final int size, final byte[] value) {
final int i = newID();
final long v = index(pre, i, value, DOC);
s(DOC); s(0); s(0); s(v >> 32);
s(v >> 24); s(v >> 16); s(v >> 8); s(v);
s(size >> 24); s(size >> 16); s(size >> 8); s(size);
s(i >> 24); s(i >> 16); s(i >> 8); s(i);
}
/**
* Adds an element entry to the internal update buffer.
* @param dist parent distance
* @param name tag name index
* @param asize number of attributes
* @param size node size
* @param uri namespace uri reference
* @param ne namespace flag
*/
public final void elem(final int dist, final int name, final int asize,
final int size, final int uri, final boolean ne) {
// build and insert new entry
final int i = newID();
final int n = ne ? 1 << 7 : 0;
s(Math.min(IO.MAXATTS, asize) << 3 | ELEM);
s(n | (byte) (name >> 8)); s(name); s(uri);
s(dist >> 24); s(dist >> 16); s(dist >> 8); s(dist);
s(size >> 24); s(size >> 16); s(size >> 8); s(size);
s(i >> 24); s(i >> 16); s(i >> 8); s(i);
}
/**
* Adds a text entry to the internal update buffer.
* @param pre pre value
* @param dist parent distance
* @param value string value
* @param kind node kind
*/
public final void text(final int pre, final int dist, final byte[] value,
final int kind) {
// build and insert new entry
final int i = newID();
final long v = index(pre, i, value, kind);
s(kind); s(0); s(0); s(v >> 32);
s(v >> 24); s(v >> 16); s(v >> 8); s(v);
s(dist >> 24); s(dist >> 16); s(dist >> 8); s(dist);
s(i >> 24); s(i >> 16); s(i >> 8); s(i);
}
/**
* Adds an attribute entry to the internal update buffer.
* @param pre pre value
* @param dist parent distance
* @param name attribute name
* @param value attribute value
* @param uri namespace uri reference
* @param ne namespace flag
*/
public final void attr(final int pre, final int dist, final int name,
final byte[] value, final int uri, final boolean ne) {
// add attribute to text storage
final int i = newID();
final long v = index(pre, i, value, ATTR);
final int n = ne ? 1 << 7 : 0;
s(Math.min(IO.MAXATTS, dist) << 3 | ATTR);
s(n | (byte) (name >> 8)); s(name); s(v >> 32);
s(v >> 24); s(v >> 16); s(v >> 8); s(v);
s(0); s(0); s(0); s(uri);
s(i >> 24); s(i >> 16); s(i >> 8); s(i);
}
/**
* Stores the specified value in the update buffer.
* @param value value to be stored
*/
private void s(final int value) {
b[bp++] = (byte) value;
}
/**
* Generates a new id.
* @return id
*/
private int newID() {
return ++meta.lastid;
}
/**
* Stores the specified value in the update buffer.
* @param value value to be stored
*/
private void s(final long value) {
b[bp++] = (byte) value;
}
/**
* Returns the byte buffer.
* @return byte buffer
*/
private byte[] buffer() {
final byte[] bb = bp == b.length ? b : Arrays.copyOf(b, bp);
bp = 0;
return bb;
}
/**
* Indexes a text and returns the reference.
* @param pre pre value
* @param id id value
* @param value text to be indexed
* @param kind node kind
* @return reference
*/
protected abstract long index(final int pre, final int id, final byte[] value,
final int kind);
/** Notify the index structures that an update operation is started. */
void indexBegin() { }
/** Notify the index structures that an update operation is finished. */
void indexEnd() { }
/**
* Delete a node and its descendants from the corresponding indexes.
* @param pre pre value of the node to delete
* @param size number of descendants
*/
protected abstract void indexDelete(final int pre, final int size);
/**
* Returns a string representation of the specified table range. Can be called
* for debugging.
* @param start start pre value
* @param end end pre value
* @return table
*/
final String toString(final int start, final int end) {
return string(InfoStorage.table(this, start, end));
}
@Override
public final String toString() {
return toString(0, meta.size);
}
}