package org.basex.build;
import static org.basex.build.BuildText.*;
import static org.basex.core.Text.*;
import static org.basex.util.Token.*;
import java.io.*;
import org.basex.core.jobs.*;
import org.basex.data.*;
import org.basex.index.name.*;
import org.basex.index.path.*;
import org.basex.index.stats.*;
import org.basex.io.*;
import org.basex.util.*;
import org.basex.util.list.*;
/**
* This class provides an interface for building database instances.
* The specified {@link Parser} sends events to this class whenever nodes
* are to be added or closed. The builder implementation decides whether
* the nodes are stored on disk or kept in memory.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public abstract class Builder extends Job {
/** Tree structure. */
final PathIndex path = new PathIndex();
/** Namespace index. */
final Namespaces nspaces = new Namespaces();
/** Parser instance. */
final Parser parser;
/** Database name. */
final String dbName;
/** Number of cached size values. */
int ssize;
/** Currently stored size value. */
int spos;
/** Meta data on built database. */
MetaData meta;
/** Element name index. */
Names elemNames;
/** Attribute name index. */
Names attrNames;
/** Parent stack. */
private final IntList parStack = new IntList();
/** Stack with element names. */
private final IntList elemStack = new IntList();
/** Current tree height. */
private int level;
/**
* Constructor.
* @param dbName name of database
* @param parser parser
*/
Builder(final String dbName, final Parser parser) {
this.dbName = dbName;
this.parser = parser;
}
// PUBLIC METHODS ===========================================================
/**
* Parses the given input source and builds the database.
* @throws IOException I/O exception
*/
final void parse() throws IOException {
final Performance perf = Prop.debug ? new Performance() : null;
Util.debug(shortInfo() + DOTS);
try {
// add document node and parse document
parser.parse(this);
} finally {
parser.close();
}
meta.lastid = meta.size - 1;
if(Prop.debug) Util.errln(" " + perf + " (" + Performance.getMemory() + ')');
}
/**
* Opens a document node.
* @param value document name
* @throws IOException I/O exception
*/
public final void openDoc(final byte[] value) throws IOException {
path.index(0, Data.DOC, level);
parStack.set(level++, meta.size);
addDoc(value);
nspaces.open();
}
/**
* Closes a document node.
* @throws IOException I/O exception
*/
public final void closeDoc() throws IOException {
final int pre = parStack.get(--level);
setSize(pre, meta.size - pre);
++meta.ndocs;
nspaces.close(meta.size);
}
/**
* Opens a new element node.
* @param name name of element
* @param att attributes
* @param nsp namespaces
* @throws IOException I/O exception
*/
public final void openElem(final byte[] name, final Atts att, final Atts nsp) throws IOException {
addElem(name, att, nsp);
++level;
}
/**
* Stores an empty element.
* @param name name of element
* @param att attributes
* @param nsp namespaces
* @throws IOException I/O exception
*/
public final void emptyElem(final byte[] name, final Atts att, final Atts nsp)
throws IOException {
addElem(name, att, nsp);
final int pre = parStack.get(level);
nspaces.close(pre);
if(att.size() > IO.MAXATTS) setSize(pre, meta.size - pre);
}
/**
* Closes an element.
* @throws IOException I/O exception
*/
public final void closeElem() throws IOException {
checkStop();
--level;
final int pre = parStack.get(level);
setSize(pre, meta.size - pre);
nspaces.close(pre);
}
/**
* Stores a text node.
* @param value text value
* @throws IOException I/O exception
*/
public final void text(final byte[] value) throws IOException {
if(value.length != 0) addText(value, Data.TEXT);
}
/**
* Stores a comment.
* @param value comment text
* @throws IOException I/O exception
*/
public final void comment(final byte[] value) throws IOException {
addText(value, Data.COMM);
}
/**
* Stores a processing instruction.
* @param pi processing instruction name and value
* @throws IOException I/O exception
*/
public final void pi(final byte[] pi) throws IOException {
addText(pi, Data.PI);
}
// PROGRESS INFORMATION =====================================================
@Override
public final String shortInfo() {
return CREATING_DB;
}
@Override
public final String detailedInfo() {
return spos == 0 ? parser.detailedInfo() : FINISHING_D;
}
@Override
public final double progressInfo() {
return spos == 0 ? parser.progressInfo() : (double) spos / ssize;
}
// ABSTRACT METHODS =========================================================
/**
* Builds the database.
* @return data database instance
* @throws IOException I/O exception
*/
public abstract Data build() throws IOException;
/**
* Returns a data clip with the parsed input.
* @return data data clip
* @throws IOException I/O exception
*/
public abstract DataClip dataClip() throws IOException;
/**
* Adds a document node to the database.
* @param value name of the document
* @throws IOException I/O exception
*/
protected abstract void addDoc(byte[] value) throws IOException;
/**
* Adds an element node to the database. This method stores a preliminary
* size value; if this node has further descendants, {@link #setSize} must
* be called to set the final size value.
* @param dist distance to parent
* @param nameId id of element name
* @param asize number of attributes
* @param uriId id of namespace uri
* @param ne namespace flag (indicates if this element introduces new namespaces)
* @throws IOException I/O exception
*/
protected abstract void addElem(int dist, int nameId, int asize, int uriId, boolean ne)
throws IOException;
/**
* Adds an attribute to the database.
* @param nameId id of attribute name
* @param value attribute value
* @param dist distance to parent
* @param uriId id of namespace uri
* @throws IOException I/O exception
*/
protected abstract void addAttr(int nameId, byte[] value, int dist, int uriId) throws IOException;
/**
* Adds a text node to the database.
* @param value the token to be added
* @param dist distance to parent
* @param kind the node kind
* @throws IOException I/O exception
*/
protected abstract void addText(byte[] value, int dist, byte kind) throws IOException;
/**
* Stores a size value to the specified table position.
* @param pre pre reference
* @param size value to be stored
* @throws IOException I/O exception
*/
protected abstract void setSize(int pre, int size) throws IOException;
// PRIVATE METHODS ==========================================================
/**
* Adds an element node to the storage.
* @param name element name
* @param atts attributes
* @param nsp namespaces
* @throws IOException I/O exception
*/
private void addElem(final byte[] name, final Atts atts, final Atts nsp) throws IOException {
// get reference of element name
int nameId = elemNames.index(name);
path.index(nameId, Data.ELEM, level);
// cache pre value
final int pre = meta.size;
// remember id of element name and parent reference
elemStack.set(level, nameId);
parStack.set(level, pre);
// parse namespaces
nspaces.open(pre, nsp);
// get and store element references
final int dis = level == 0 ? 1 : pre - parStack.get(level - 1);
final int as = atts.size();
final byte[] pref = prefix(name);
int uriId = nspaces.uriIdForPrefix(pref, true);
if(uriId == 0 && pref.length != 0 && !eq(pref, XML))
throw new BuildException(WHICHNS, parser.detailedInfo(), prefix(name));
addElem(dis, nameId, Math.min(IO.MAXATTS, as + 1), uriId, !nsp.isEmpty());
// get and store attribute references
for(int a = 0; a < as; ++a) {
final byte[] an = atts.name(a), av = atts.value(a), ap = prefix(an);
nameId = attrNames.index(an, av);
uriId = nspaces.uriIdForPrefix(ap, false);
if(uriId == 0 && ap.length != 0 && !eq(ap, XML))
throw new BuildException(WHICHNS, parser.detailedInfo(), an);
path.index(nameId, Data.ATTR, level + 1, av, meta);
addAttr(nameId, av, Math.min(IO.MAXATTS, a + 1), uriId);
}
// set leaf node information in index
if(level > 1) elemNames.stats(elemStack.get(level - 1)).setLeaf(false);
// check if data ranges exceed database limits, based on the storage details in {@link Data}
limit(elemNames.size(), 0x8000, LIMITELEMS);
limit(attrNames.size(), 0x8000, LIMITATTS);
limit(nspaces.size(), 0x100, LIMITNS);
if(meta.size < 0) limit(0, 0, LIMITRANGE);
}
/**
* Checks a value limit and optionally throws an exception.
* @param value value
* @param limit limit
* @param message error message
* @throws IOException I/O exception
*/
private void limit(final int value, final int limit, final String message) throws IOException {
if(value >= limit) throw new BuildException(message, parser.detailedInfo(), limit);
}
/**
* Adds a simple text, comment or processing instruction to the database.
* @param value the value to be added
* @param kind the node type
* @throws IOException I/O exception
*/
private void addText(final byte[] value, final byte kind) throws IOException {
final int l = level;
if(l > 1) {
// add text node to statistics, or set leaf flag
final Stats stats = elemNames.stats(elemStack.get(l - 1));
if(kind == Data.TEXT) stats.add(value, meta);
else stats.setLeaf(false);
}
path.index(0, kind, l, value, meta);
addText(value, l == 0 ? 1 : meta.size - parStack.get(l - 1), kind);
}
}