package org.basex.data;
import static org.basex.core.Text.*;
import static org.basex.data.DataText.*;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.basex.io.in.DataInput;
import org.basex.io.out.DataOutput;
import org.basex.util.Table;
import org.basex.util.Token;
import org.basex.util.TokenBuilder;
import org.basex.util.hash.TokenObjMap;
import org.basex.util.hash.TokenSet;
import org.basex.util.list.IntList;
import org.basex.util.list.TokenList;
/**
* This class contains the namespaces of a database.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class Namespaces {
/** Namespace stack. */
private final IntList uriStack = new IntList();
/** Prefixes. */
private final TokenSet pref;
/** URIs. */
private final TokenSet uri;
/** Root node. */
final NSNode root;
/** New namespace flag. */
private boolean newns;
/** Current level. Index starts at 1 to reserve an additional level for
* XQUP insert operations. */
private int uriL = 1;
/** Current namespace node. */
NSNode current;
// Building Namespaces ======================================================
/**
* Empty constructor.
*/
public Namespaces() {
pref = new TokenSet();
uri = new TokenSet();
root = new NSNode(-1);
current = root;
}
/**
* Constructor, specifying an input stream.
* @param in input stream
* @throws IOException I/O exception
*/
Namespaces(final DataInput in) throws IOException {
pref = new TokenSet(in);
uri = new TokenSet(in);
root = new NSNode(in, null);
current = root;
}
/**
* Writes the namespaces to disk.
* @param out output stream
* @throws IOException I/O exception
*/
void write(final DataOutput out) throws IOException {
pref.write(out);
uri.write(out);
root.write(out);
}
/**
* Adds the specified namespace to the namespace structure
* and changes the root node. Needed for building the namespace structure.
* @param p prefix
* @param u uri
* @param pre pre value
* @return new NSNode if a new one has been created, or null otherwise
*/
public NSNode add(final byte[] p, final byte[] u, final int pre) {
NSNode newNode = null;
// after open() -call, newns==false
if(!newns) {
newNode = new NSNode(pre);
current = current.add(newNode);
newns = true;
}
final int k = addPrefix(p);
final int v = addURI(u);
current.add(k, v);
if(p.length == 0) uriStack.set(uriL, v);
return newNode;
}
/**
* Opens an element. Needed for building the namespace structure.
* @return true if a new namespace has been added
*/
public boolean open() {
uriStack.set(uriL + 1, uriStack.get(uriL));
++uriL;
final boolean n = newns;
newns = false;
return n;
}
/**
* Closes a node. Needed for building the namespace structure.
* @param pre current pre value
*/
public void close(final int pre) {
while(current.pre >= pre && current.par != null) current = current.par;
--uriL;
uriStack.set(uriL, uriStack.get(uriL - 1));
}
/**
* Returns the namespace uri reference for the specified name,
* or 0 if namespace cannot be found.
* @param n tag/attribute name
* @param elem element flag
* @return namespace
*/
public int uri(final byte[] n, final boolean elem) {
if(uri.size() == 0) return 0;
final byte[] pr = Token.prefix(n);
int u = elem ? uriStack.get(uriL) : 0;
if(pr.length != 0) u = uri(pr, current);
return u;
}
// Requesting Namespaces ====================================================
/**
* Returns the number of uri references.
* @return number of uri references
*/
public int size() {
/* returns the size of the uri container - if we delete nodes from
* the namespace structure via delete(pre,s) the container size isn't
* changed at all, as only NSNodes in the range pre,pre+s-1 are deleted.
* COUNTERINTUITIVE?
*/
return uri.size();
}
/**
* Returns the default namespace of the database, or {@code null}
* if several (default or prefixed) namespaces are defined.
* @return global default namespace
*/
public byte[] globalNS() {
// no namespaces defined: default namespace is empty
if(root.size == 0) return Token.EMPTY;
// more than one namespace defined: skip test
if(root.size > 1) return null;
// check namespaces of first child
final NSNode n = root.ch[0];
// namespace has more children; skip traversal
if(n.size != 0 || n.pre != 1 || n.vals.length != 2) return null;
// return default namespace or null
return pref.key(n.vals[0]).length == 0 ? uri.key(n.vals[1]) : null;
}
/**
* Returns the specified namespace uri.
* @param id namespace uri reference
* @return prefix
*/
public byte[] uri(final int id) {
return uri.key(id);
}
/**
* Returns the namespace URI reference for the specified QName and pre value.
* @param name tag/attribute name
* @param pre pre value
* @return namespace URI reference or 0 if no namespace was found
*/
public int uri(final byte[] name, final int pre) {
return uri(Token.prefix(name), current.find(pre));
}
/**
* Deletes the specified namespace URI from the root node.
* @param u namespace URI reference
*/
public void delete(final byte[] u) {
final int id = uri.id(u);
if(id != 0) current.delete(id);
}
/**
* Returns the specified prefix.
* @param id prefix reference
* @return prefix
*/
byte[] prefix(final int id) {
return pref.key(id);
}
/**
* Returns the prefix and URI references for the specified pre value.
* @param pre pre value
* @return namespace references
*/
int[] get(final int pre) {
return current.find(pre).vals;
}
/**
* Returns the namespace URI reference for the specified prefix and node,
* or 0 if namespace cannot be found.
* @param pr prefix
* @param nd node to start with
* @return namespace
*/
private int uri(final byte[] pr, final NSNode nd) {
if(Token.eq(Token.XML, pr)) return 0;
final int i = pref.id(pr);
if(i == 0) return 0;
NSNode n = nd;
while(n != null) {
final int u = n.uri(i);
if(u != 0) return u;
n = n.par;
}
return 0;
}
// Updating Namespaces ======================================================
/**
* Deletes the specified number of entries from the namespace structure.
* @param pre pre value of the first node to delete
* @param size number of entries to be deleted
*/
void delete(final int pre, final int size) {
NSNode nd = current.find(pre);
if(nd.pre == pre) nd = nd.par;
while(nd != null) {
nd.delete(pre, size);
nd = nd.par;
}
}
/**
* Adds a namespace for the specified pre value.
* @param pre pre value
* @param par parent
* @param p prefix
* @param u uri
* @return uri reference
*/
public int add(final int pre, final int par, final byte[] p, final byte[] u) {
final NSNode nd = current.find(par);
final NSNode t = new NSNode(pre);
final int k = addPrefix(p);
final int v = addURI(u);
if(nd.pre == pre) {
nd.add(k, v);
} else {
t.add(k, v);
nd.add(t);
}
return v;
}
/**
* Adds the specified namespace uri.
* @param u namespace uri to be added
* @return reference
*/
public int addURI(final byte[] u) {
return Math.abs(uri.add(u));
}
/**
* Adds the specified prefix.
* @param p prefix to be added
* @return reference
*/
private int addPrefix(final byte[] p) {
return Math.abs(pref.add(p));
}
/**
* This is only called when a MemData instance is inserted. The
* namespace node which is next on the ancestor axis of the insert location
* is set as new root.
* @param n nearest namespace node on ancestor axis
* @param pre pre value to find nearest namespace node for
*/
void setNearestRoot(final NSNode n, final int pre) {
final int uriI = uri(Token.EMPTY, pre);
uriStack.set(uriL, uriI);
// remind uri before insert of first node n to connect siblings of n to
// according namespace
uriStack.set(uriL - 1, uriI);
current = n;
}
/**
* Setter for namespaces root node.
* @param n new root
*/
void setRoot(final NSNode n) {
current = n;
}
/**
* Updates the pre values of all NSNodes on the following axis after a
* structural update at location pre.
* @param pre update location
* @param ms size of inserted/deleted node
* @param insert true if insert operation, false if delete
* @param nodes new nodes that have been added as part of delete/insert
*/
void update(final int pre, final int ms, final boolean insert,
final Set<NSNode> nodes) {
update(root, pre, ms, insert, nodes != null ? nodes :
new HashSet<NSNode>());
}
/**
* Updates the pre values of all NSNodes on the following axis after a
* structural update at location pre.
* @param n current namespace node which is updated if necessary
* @param pre update location
* @param ms size of inserted/deleted node
* @param insert true if insert operation, false if delete
* @param nodes new nodes that have been added as part of delete/insert
*/
private static void update(final NSNode n, final int pre, final int ms,
final boolean insert, final Set<NSNode> nodes) {
if(!nodes.contains(n) && n.pre >= (insert ? pre : pre + ms))
n.pre += insert ? ms : ms * -1;
for(int c = 0; c < n.size; c++) update(n.ch[c], pre, ms, insert, nodes);
}
// Printing Namespaces ======================================================
/**
* Returns a tabular representation of the namespaces.
* @param s start pre value
* @param e end pre value
* @return namespaces
*/
public byte[] table(final int s, final int e) {
if(root.size == 0) return Token.EMPTY;
final Table t = new Table();
t.header.add(TABLEID);
t.header.add(TABLEPRE);
t.header.add(TABLEDIST);
t.header.add(TABLEPREF);
t.header.add(TABLEURI);
for(int i = 0; i < 3; ++i) t.align.add(true);
table(t, root, s, e);
return t.contents.isEmpty() ? Token.EMPTY : t.finish();
}
/**
* Adds the namespace structure for a node to the specified table.
* @param t table
* @param n namespace node
* @param s start pre value
* @param e end pre value
*/
private void table(final Table t, final NSNode n, final int s, final int e) {
for(int i = 0; i < n.vals.length; i += 2) {
if(n.pre < s || n.pre > e) continue;
final TokenList tl = new TokenList();
tl.add(n.vals[i + 1]);
tl.add(n.pre);
tl.add(n.pre - n.par.pre);
tl.add(pref.key(n.vals[i]));
tl.add(uri.key(n.vals[i + 1]));
t.contents.add(tl);
}
for(int i = 0; i < n.size; i++) table(t, n.ch[i], s, e);
}
/**
* Returns namespace information.
* @return info string
*/
public byte[] info() {
final TokenObjMap<TokenList> map = new TokenObjMap<TokenList>();
info(map, root);
final TokenBuilder tb = new TokenBuilder();
for(final byte[] val : map.keys()) {
tb.add(" ");
final TokenList key = map.get(val);
key.sort(false);
final int ks = key.size();
if(ks > 1 || key.get(0).length != 0) {
if(key.size() != 1) tb.add("(");
for(int k = 0; k < ks; ++k) {
if(k != 0) tb.add(", ");
tb.add(key.get(k));
}
if(ks != 1) tb.add(")");
tb.add(" = ");
}
tb.addExt("\"%\"" + NL, val);
}
return tb.finish();
}
/**
* Adds namespace information for the specified node to a map.
* @param map namespace map
* @param n namespace node
*/
private void info(final TokenObjMap<TokenList> map, final NSNode n) {
for(int i = 0; i < n.vals.length; i += 2) {
final byte[] key = uri.key(n.vals[i + 1]);
final byte[] val = pref.key(n.vals[i]);
TokenList old = map.get(key);
if(old == null) {
old = new TokenList();
map.add(key, old);
}
if(!old.contains(val)) old.add(val);
}
for(final NSNode c : n.ch) info(map, c);
}
/**
* Returns a string representation of the namespaces.
* @param s start pre value
* @param e end pre value
* @return string
*/
public String toString(final int s, final int e) {
return root.print(this, s, e);
}
@Override
public String toString() {
return toString(0, Integer.MAX_VALUE);
}
}