package org.basex.data; import static org.basex.util.Token.*; import java.io.*; import java.util.*; import org.basex.core.*; import org.basex.index.*; import org.basex.index.name.*; import org.basex.index.path.*; import org.basex.index.query.*; import org.basex.index.resource.*; import org.basex.index.value.*; import org.basex.io.*; import org.basex.io.random.*; import org.basex.util.*; import org.basex.util.list.*; /** * This class represents a database instance. It provides low-level access to all * properties and values stored in a single database. * * An XML node is accessed by its {@code pre} value, which is not stored itself, but * given by the table position. 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 element and attribute names is allowed</li> * <li>A maximum of 2^8 different namespaces is allowed</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> * * As all methods of this class are optimized for performance, no checks are * performed on the arguments (e.g.: if the string value of a text node is * requested, the specified pre value must point to a text node). * * NOTE: the class is not thread-safe. It is imperative that all read/write accesses * are synchronized over a single context's read/write lock. * * @author BaseX Team 2005-17, BSD License * @author Christian Gruen */ public abstract class Data { /** Node kind: document (code: {@code 0}). */ public static final byte DOC = 0x00; /** Node kind: element (code: {@code 1}). */ public static final byte ELEM = 0x01; /** Node kind: text (code: {@code 2}). */ public static final byte TEXT = 0x02; /** Node kind: attribute (code: {@code 3}). */ public static final byte ATTR = 0x03; /** Node kind: comment (code: {@code 4}). */ public static final byte COMM = 0x04; /** Node kind: processing instruction (code: {@code 5}). */ public static final byte PI = 0x05; /** Resource index. */ public final Resources resources = new Resources(this); /** Meta data. */ public final MetaData meta; /** Element names. */ public Names elemNames; /** Attribute names. */ public Names attrNames; /** Namespace index. */ public Namespaces nspaces; /** Path index. */ public PathIndex paths; /** Text index. */ public ValueIndex textIndex; /** Attribute value index. */ public ValueIndex attrIndex; /** Token index. */ public ValueIndex tokenIndex; /** Full-text index. */ public ValueIndex ftIndex; /** Indicates if distances are to be updated. */ public boolean updateDists = true; /** ID->PRE mapping. */ public IdPreMap idmap; /** Table access file. */ protected TableAccess table; /** Closed flag. */ protected boolean closed; /** * Default constructor. * @param meta meta data */ protected Data(final MetaData meta) { this.meta = meta; } /** * Closes the database. */ public void close() { closed = true; } /** * Indicates if the database has been closed. * @return result of check */ public final boolean closed() { return closed; } /** * Drops the specified index. * @param type index to be dropped * @param cmd calling command * @throws IOException I/O exception */ public abstract void createIndex(IndexType type, Command cmd) throws IOException; /** * Drops the specified index. * @param type index to be dropped * @throws BaseXException database exception */ public abstract void dropIndex(IndexType type) throws BaseXException; /** * Starts an update operation: writes a file to disk to indicate that an update is going on, * and exclusively locks the table file. * @param opts main options * @throws BaseXException database exception */ public abstract void startUpdate(MainOptions opts) throws BaseXException; /** * Finishes an update operation: removes the update file and the exclusive lock. * @param opts main options */ public abstract void finishUpdate(MainOptions opts); /** * Flushes updated data. * @param all flush all data */ public abstract void flush(boolean all); /** * Returns an index iterator for the specified token. * @param token index token reference * @return index iterator */ public final IndexIterator iter(final IndexToken token) { return index(token.type()).iter(token); } /** * Returns a cost estimation for searching the specified token. * Smaller values are better, a value of zero indicates that no results will be returned. * @param token text to be found * @return cost estimation */ public final int costs(final IndexToken token) { return index(token.type()).costs(token); } /** * Returns info on the specified index structure. * @param type index type * @param options main options * @return info */ public final byte[] info(final IndexType type, final MainOptions options) { return index(type).info(options); } /** * Returns an index for the specified index type. * @param type index type * @return index */ public final Index index(final IndexType type) { switch(type) { case ELEMNAME: return elemNames; case ATTRNAME: return attrNames; case TEXT: return textIndex; case ATTRIBUTE: return attrIndex; case TOKEN: return tokenIndex; case FULLTEXT: return ftIndex; 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 for the specified id. * @param id unique node id * @return pre value or {@code -1} if id was not found */ public final int pre(final int id) { if(meta.updindex) return idmap.pre(id); // find pre value in the table; start with specified id final int size = meta.size; for(int p = Math.max(0, id); p < meta.size; ++p) if(id == id(p)) return p; final int ps = Math.min(size, id); for(int p = 0; p < ps; ++p) if(id == id(p)) return p; // id not found return -1; } /** * 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 the node kind, which may be {@link #DOC}, {@link #ELEM}, {@link #TEXT}, * {@link #ATTR}, {@link #COMM} or {@link #PI}. * @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 */ public final 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, or {@code null} */ 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(nameId(p) == att) return text(p, false); return null; } /** * Returns the id of the name of an element, attribute or processing instruction. * @param pre pre value * @return name id */ public final int nameId(final int pre) { return table.read2(pre, 1) & 0x7FFF; } /** * Returns the name of an element, attribute or processing instruction. * @param pre pre value * @param kind node kind * @return name */ 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 ? elemNames : attrNames).key(nameId(pre)); } /** * Returns the id of the namespace uri of the addressed element or attribute. * @param pre pre value * @param kind node kind * @return id of the namespace uri */ public final int uriId(final int pre, final int kind) { return kind == ELEM || kind == ATTR ? table.read1(pre, kind == ELEM ? 3 : 11) & 0xFF : 0; } /** * Returns the name and namespace uri of the addressed element or attribute. * @param pre pre value * @param kind node kind * @return array with name and namespace uri */ public final byte[][] qname(final int pre, final int kind) { final byte[] name = name(pre, kind); byte[] uri = null; final boolean hasPrefix = indexOf(name, ':') != -1; if(hasPrefix || !nspaces.isEmpty()) { final int uriId = uriId(pre, kind); if(uriId > 0) { uri = nspaces.uri(uriId); } else if(hasPrefix && eq(prefix(name), XML)) { uri = DataText.XML_URI; } } return new byte[][] { name, uri == null ? EMPTY : uri }; } /** * 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 prefixes and uris that are defined for the specified pre value. * Should only be called for element nodes. * @param pre pre value * @return key and value ids */ public final Atts namespaces(final int pre) { return nsFlag(pre) ? nspaces.values(pre, this) : new Atts(); } /** * Returns the reference to a text (text, comment, pi, pi, document) or attribute value. * @param pre pre value * @return disk offset */ public final long textRef(final int pre) { return table.read5(pre, 3); } /** * Returns a text (text, comment, pi, document) 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, document) or attribute value as integer value. * {@link Long#MIN_VALUE} is returned if the input is no valid integer. * @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, document) or attribute value as double value. * {@link Double#NaN} is returned if the input is no valid double. * @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 (possibly compressed) text (text, comment, pi, document). * @param pre pre value * @param text text/attribute flag * @return length */ public abstract int textLen(int pre, boolean text); // UPDATE OPERATIONS ======================================================== /** * Updates (renames) the name of an element, attribute or processing instruction. * @param pre pre value of the node to be updated * @param kind node kind of the updated node * @param name name of the new element, attribute or processing instruction * @param uri namespace 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))), PI); } else { // check if namespace has changed final byte[] prefix = prefix(name); final int oldUriId = nspaces.uriIdForPrefix(prefix, pre, this); final boolean nsFlag = oldUriId == 0 && uri.length != 0 && !eq(prefix, XML); final int nsPre = kind == ATTR ? parent(pre, kind) : pre; final int uriId = nsFlag ? nspaces.add(nsPre, prefix, uri, this) : oldUriId != 0 && eq(nspaces.uri(oldUriId), uri) ? oldUriId : 0; final int sz = size(pre, kind); // write ids of namespace uri and name, and namespace flag if(kind == ATTR) { // delete old values from attribute indexes if(meta.updindex) { if(meta.attrindex) attrIndex.delete(new ValueCache(pre, IndexType.ATTRIBUTE, this)); if(meta.tokenindex) tokenIndex.delete(new ValueCache(pre, IndexType.TOKEN, this)); } table.write1(pre, 11, uriId); table.write2(pre, 1, attrNames.put(name)); if(nsFlag) table.write2(nsPre, 1, 1 << 15 | nameId(nsPre)); // add new values to attribute indexes if(meta.updindex) { if(meta.attrindex) attrIndex.add(new ValueCache(pre, IndexType.ATTRIBUTE, this)); if(meta.tokenindex) tokenIndex.add(new ValueCache(pre, IndexType.TOKEN, this)); } } else { // update element name final IntList pres = new IntList(); // update text index if(meta.updindex && meta.textindex) { final int last = pre + sz; for(int curr = pre + attSize(pre, kind); curr < last; curr += size(curr, kind(curr))) { if(kind(curr) == TEXT) pres.add(curr); } textIndex.delete(new ValueCache(pres, IndexType.TEXT, this)); } table.write1(pre, 3, uriId); final int nameId = elemNames.put(name); table.write2(nsPre, 1, (nsFlag || nsFlag(nsPre) ? 1 << 15 : 0) | nameId); if(!pres.isEmpty()) textIndex.add(new ValueCache(pres, IndexType.TEXT, this)); } } } /** * Updates (replaces) the value of a single text, comment, pi, attribute or document node. * @param pre pre value of the node to be updated * @param kind node kind * @param value value to be updated (text, comment, pi, attribute, document name) */ public final void update(final int pre, final int kind, final byte[] value) { final byte[] val = kind == PI ? trim(concat(name(pre, kind), SPACE, value)) : value; if(eq(val, text(pre, kind != ATTR))) return; meta.update(); updateText(pre, val, kind); if(kind == DOC) resources.rename(pre, value); } /** * Rapid Replace implementation. Replaces parts of the database with the specified data instance. * @param pre pre value of the node to be replaced * @param source clip with source data */ public final void replace(final int pre, final DataClip source) { meta.update(); final int sCount = source.size(); final int tKind = kind(pre); final int tSize = size(pre, tKind); final int tPar = parent(pre, tKind); bufferSize(sCount); // update index structures indexDelete(pre, id(pre), tSize); final Data sData = source.data; int sTopPre = source.start; for(int sPre = source.start; sPre < source.end; ++sPre) { // properties of the source node final int sKind = sData.kind(sPre); final int sSize = sData.size(sPre, sKind); final int sPar = sData.parent(sPre, sKind); final int cPre = pre + sPre - source.start; // calculate new distance value final int cDist; if(sPre == sTopPre) { // handle top level entry: calculate distance based on target database cDist = cPre - tPar; // calculate pre value of next top level entry sTopPre += sSize; } else { cDist = sPre - sPar; } switch(sKind) { case DOC: // add document doc(sSize, sData.text(sPre, true)); ++meta.ndocs; break; case ELEM: // add element final byte[] en = sData.name(sPre, sKind); elem(cDist, elemNames.put(en), sData.attSize(sPre, sKind), sSize, nspaces.uriIdForPrefix(prefix(en), true), false); break; case TEXT: case COMM: case PI: // add text text(cDist, sData.text(sPre, true), sKind); break; case ATTR: // add attribute final byte[] an = sData.name(sPre, sKind); attr(cDist, attrNames.put(an), sData.text(sPre, false), nspaces.uriIdForPrefix(prefix(an), false)); break; } } // replace table entries, reset buffer size table.replace(pre, buffer(), tSize); bufferSize(1); // if necessary, increase/decrease size of ancestors and adjust distances of siblings final int diff = sCount - tSize; if(diff != 0) { int p = tPar; while(p >= 0) { final int k = kind(p); size(p, k, size(p, k) + diff); p = parent(p, k); } updateDist(pre + sCount, diff); } // adjust attribute size of parent if attributes inserted. attribute size // of parent cannot be decreased via a replace expression. int sPre = source.start; if(sData.kind(sPre) == ATTR) { int d = 0; while(sPre < source.end && sData.kind(sPre++) == ATTR) d++; if(d > 1) attSize(tPar, kind(tPar), d + attSize(tPar, ELEM) - 1); } // add entries to index structures indexAdd(pre, meta.lastid - sCount + 1, sCount, source); } /** * Deletes a node and its descendants. * @param pre pre value of the node to be deleted */ public final void delete(final int pre) { meta.update(); // delete references in document index int kind = kind(pre); final int size = size(pre, kind); // delete entries in value indexes indexDelete(pre, id(pre), size); /// delete textual values if(kind != DOC && kind != ELEM) delete(pre, kind != ATTR); // reduce size of ancestors int par = pre; // check if we are an attribute (different size counters) if(kind == ATTR) { par = parent(par, ATTR); attSize(par, ELEM, attSize(par, ELEM) - 1); size(par, ELEM, size(par, ELEM) - 1); kind = kind(par); } // delete namespace nodes and propagate pre value shifts (before node sizes are touched!) nspaces.delete(pre, size, this); // reduce size of ancestors while(par > 0 && kind != DOC) { par = parent(par, kind); kind = kind(par); size(par, kind, size(par, kind) - size); } // preserve empty root node if(kind(pre) == DOC) --meta.ndocs; // delete node from table structure and reduce document size table.delete(pre, size); updateDist(pre, -size); } /** * Inserts standalone attributes (without root element). * @param pre target pre value (insertion position) * @param par target parent pre value of node * @param source clip with source data */ public final void insertAttr(final int pre, final int par, final DataClip source) { // #1168/2: store one by one (otherwise, namespace declarations may be added more than once) for(int s = 0; s < source.fragments; s++) { final int start = source.start + s; insert(pre + s, par, new DataClip(source.data, start, start + 1)); } attSize(par, ELEM, attSize(par, ELEM) + source.size()); } /** * Inserts a data instance at the specified pre value. Notes: * <ul> * <li> The data instance in the specified data clip must differ from this instance.</li> * <li> Attributes must be inserted via {@link #insertAttr}.</li> * </ul> * @param pre target pre value (insertion position) * @param par target parent pre value of node ({@code -1} if document is added) * @param source clip with source data */ public final void insert(final int pre, final int par, final DataClip source) { final int sCount = source.size(); if(sCount == 0) return; meta.update(); resources.docs(); // resize buffer to cache more entries final int bSize = Math.min(sCount, IO.BLOCKSIZE >> IO.NODEPOWER); bufferSize(bSize); // organize namespaces to avoid duplicate declarations final NSScope nsScope = new NSScope(pre, this); // indicates if database only contains a dummy node final Data sdata = source.data; int c = 0, sTopPre = source.start; for(int sPre = sTopPre; sPre < source.end; ++sPre, ++c) { if(c != 0 && c % bSize == 0) insert(pre + c - bSize); // values of source node final int sKind = sdata.kind(sPre); final int sSize = sdata.size(sPre, sKind); final int sPar = sdata.parent(sPre, sKind); // pre and dist value of new node final int nPre = pre + c, nDist; if(sPre == sTopPre) { // handle top level entry: calculate distance based on target database nDist = nPre - par; // calculate pre value of next top level entry sTopPre += sSize; } else { // handle descendant node: calculate distance based on source database nDist = sPre - sPar; } // documents: use -1 as namespace root final int nsPre = sKind == DOC ? -1 : nPre - nDist; nsScope.loop(nsPre, c); switch(sKind) { case DOC: // add document nsScope.open(nPre); doc(sSize, sdata.text(sPre, true)); ++meta.ndocs; break; case ELEM: { // add element. final boolean nsFlag = nsScope.open(nPre, sdata.namespaces(sPre)); final byte[] name = sdata.name(sPre, sKind); elem(nDist, elemNames.put(name), sdata.attSize(sPre, sKind), sSize, nspaces.uriIdForPrefix(prefix(name), true), nsFlag); break; } case TEXT: case COMM: case PI: // add text, comment or processing instruction text(nDist, sdata.text(sPre, true), sKind); break; case ATTR: // add attribute final byte[] name = sdata.name(sPre, sKind); int uriId = sdata.uriId(sPre, sKind); // extend namespace scope and write namespace flag if attribute has a new namespaces if(uriId != 0) { final byte[] prefix = prefix(name), uri = sdata.nspaces.uri(uriId); uriId = nspaces.uriIdForPrefix(prefix, false); if(uriId == 0 && !eq(prefix, XML)) { uriId = nspaces.add(nsPre, prefix, uri, this); table.write2(nsPre, 1, 1 << 15 | nameId(nsPre)); } } attr(nDist, attrNames.put(name), sdata.text(sPre, false), uriId); } nsScope.shift(1); } // finalize and update namespace structure nsScope.close(); // write final entries and reset buffer if(bp != 0) insert(pre + c - 1 - (c - 1) % bSize); bufferSize(1); // increase size of ancestors int cPre = par; while(cPre >= 0) { final int cKind = kind(cPre); size(cPre, cKind, size(cPre, cKind) + sCount); cPre = parent(cPre, cKind); } // update index structures indexAdd(pre, id(pre), sCount, source); // finally, update distances updateDist(pre + sCount, sCount); } /** * 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) { if(updateDists) { int p = pre; while(p < meta.size) { final int k = kind(p); if(k == DOC) break; dist(p, k, dist(p, k) + size); p += size(p, k); } } } /** * Sets the node id. * @param pre pre value * @param value value to be stored */ public final void id(final int pre, final int value) { table.write4(pre, 12, value); } /** * Sets the size value. * @param pre pre value * @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 reference to a text/attribute value. * @param pre pre value * @param off offset */ protected final void textRef(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(int pre, byte[] value, int kind); /** * Sets the distance. * @param pre pre value * @param kind node kind * @param value value */ public final 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) { // the magic value 31 is used to signal that there are 31 or more attributes if(kind == ELEM) table.write1(pre, 0, Math.min(value, IO.MAXATTS) << 3 | ELEM); } /** * Sets the namespace flag. * Should be only called for element nodes. * @param pre pre value * @param nsFlag namespace flag */ public final void nsFlag(final int pre, final boolean nsFlag) { table.write1(pre, 1, table.read1(pre, 1) & 0x7F | (nsFlag ? 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(int pre, 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 */ private void bufferSize(final int size) { final int bs = size << IO.NODEPOWER; if(b.length != bs) b = new byte[bs]; } /** * Adds a document to the internal update buffer. * @param size node size * @param value document name */ public final void doc(final int size, final byte[] value) { final int id = newID(); final long v = textRef(value, true); 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(id >> 24); s(id >> 16); s(id >> 8); s(id); } /** * Adds an element to the internal update buffer. * @param dist parent distance * @param nameId id of element name * @param asize number of attributes * @param size node size * @param uriId id of namespace uri * @param nsFlag namespace flag */ public final void elem(final int dist, final int nameId, final int asize, final int size, final int uriId, final boolean nsFlag) { // build and insert new entry final int id = newID(); final int n = nsFlag ? 1 << 7 : 0; s(Math.min(IO.MAXATTS, asize) << 3 | ELEM); s(n | (byte) (nameId >> 8)); s(nameId); s(uriId); s(dist >> 24); s(dist >> 16); s(dist >> 8); s(dist); s(size >> 24); s(size >> 16); s(size >> 8); s(size); s(id >> 24); s(id >> 16); s(id >> 8); s(id); } /** * Adds a text, comment or processing instruction to the internal update buffer. * @param dist parent distance * @param value string value * @param kind node kind */ public final void text(final int dist, final byte[] value, final int kind) { // build and insert new entry final int id = newID(); final long v = textRef(value, true); 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(id >> 24); s(id >> 16); s(id >> 8); s(id); } /** * Adds an attribute to the internal update buffer. * @param dist parent distance * @param nameId id of attribute name * @param value attribute value * @param uriId id of namespace uri */ public final void attr(final int dist, final int nameId, final byte[] value, final int uriId) { // add attribute to text storage final int id = newID(); final long v = textRef(value, false); s(Math.min(IO.MAXATTS, dist) << 3 | ATTR); s(nameId >> 8); s(nameId); s(v >> 32); s(v >> 24); s(v >> 16); s(v >> 8); s(v); s(0); s(0); s(0); s(uriId); s(id >> 24); s(id >> 16); s(id >> 8); s(id); } /** * 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; } /** * Generates a reference for a text (text, comment, pi, pi, document) or attribute value. * @param value text to be indexed * @param text text/attribute flag * @return reference */ protected abstract long textRef(byte[] value, boolean text); /** * Deletes entries from the index structures. * @param pre first pre value of the nodes to delete * @param id id (a simple value update is indicated by the value {@code -1}) * @param size number of descendants */ protected final void indexDelete(final int pre, final int id, final int size) { if(id != -1) resources.delete(pre, size); if(meta.updindex) { if(meta.textindex) textIndex.delete(new ValueCache(pre, size, IndexType.TEXT, this)); if(meta.attrindex) attrIndex.delete(new ValueCache(pre, size, IndexType.ATTRIBUTE, this)); if(meta.tokenindex) tokenIndex.delete(new ValueCache(pre, size, IndexType.TOKEN, this)); if(id != -1) idmap.delete(pre, id, -size); } } /** * Inserts new entries in the index structure. * @param pre first pre value of the nodes to insert * @param id id (a simple value update is indicated by the value {@code -1}) * @param size number of descendants * @param clip data clip to be inserted */ protected final void indexAdd(final int pre, final int id, final int size, final DataClip clip) { if(id != -1) resources.insert(pre, clip); if(meta.updindex) { if(id != -1) idmap.insert(pre, id, size); if(meta.textindex) textIndex.add(new ValueCache(pre, size, IndexType.TEXT, this)); if(meta.attrindex) attrIndex.add(new ValueCache(pre, size, IndexType.ATTRIBUTE, this)); if(meta.tokenindex) tokenIndex.add(new ValueCache(pre, size, IndexType.TOKEN, this)); } } // HELPER FUNCTIONS =================================================================== /** * Indicates if this data instance is in main memory or on disk. * @return result of check */ public abstract boolean inMemory(); @Override public final String toString() { final int max = 20; final DataPrinter dp = new DataPrinter(this); dp.add(0, max); return meta.size > max ? dp + Text.DOTS : dp.toString(); } }