package org.basex.index; import org.basex.util.*; import org.basex.util.hash.*; import org.basex.util.list.*; /** * This class indexes keys in a balanced binary tree, including their id values. * Iterator methods are available to traverse through the tree. * * @author BaseX Team 2005-17, BSD License * @author Christian Gruen * @author Sebastian Gath */ public class IndexTree { /** Factor for resize. */ protected static final double FACTOR = 1.2; /** Keys saved in the tree. */ public final TokenList keys = new TokenList(FACTOR); /** Compressed id values. */ public TokenList ids = new TokenList(FACTOR); /** Mapping for using existing tree. */ protected TokenIntMap maps = new TokenIntMap(); /** Current iterator node. */ protected int cn; /** Tree structure [left, right, parent]. */ private final IntList tree = new IntList(FACTOR); /** Indicates which nodes have been modified. */ private final BoolList mod = new BoolList(); /** Tokenize keys. */ private final boolean tokenize; /** Tree root node. */ private int root = -1; /** * Constructor. * @param type index type */ public IndexTree(final IndexType type) { tokenize = type == IndexType.TOKEN; } /** * Indexes the specified key and id. * @param key key to be indexed * @param id id to be indexed * @param pos token position (only relevant for token index) */ public final void add(final byte[] key, final int id, final int pos) { add(key, id, pos, true); } /** * Indexes the specified key and id. If the key has already been * indexed, its id is appended to the existing array. * Otherwise, a new index entry is created. * @param key key to be indexed * @param id id to be indexed * @param pos token position (only relevant for token index) * @param exist flag for using existing index * @return int node */ protected final int add(final byte[] key, final int id, final int pos, final boolean exist) { // index is empty.. create root node if(root == -1) { root = newNode(key, id, pos, -1, exist); return root; } int n = root; while(true) { final int diff = Token.diff(key, keys.get(n)); if(diff == 0) { if(exist) { addIds(id, pos, n); } else { final int i = maps.get(Num.num(n)); if(i < 0) { maps.put(Num.num(n), ids.size()); addNewIds(id, pos); } else { addIds(id, pos, i); } } return n; } int ch = diff < 0 ? left(n) : right(n); if(ch == -1) { ch = newNode(key, id, pos, n, exist); if(diff < 0) { setLeft(n, ch); adjust(left(n)); } else { setRight(n, ch); adjust(right(n)); } return ch; } n = ch; } } /** * Returns the number of entries. * @return number of entries */ public final int size() { return ids.size(); } /** * Initializes the index iterator. * will be removed to save memory. */ public final void init() { cn = root; if(cn != -1) while(left(cn) != -1) cn = left(cn); } /** * Checks if the iterator returns more keys. * @return true if more keys exist */ public final boolean more() { return cn != -1; } /** * Returns the next pointer. * @return next pointer */ public final int next() { /* Last iterator node. */ final int ln = cn; if(right(cn) == -1) { int t = cn; cn = parent(cn); while(cn != -1 && t == right(cn)) { t = cn; cn = parent(cn); } } else { cn = right(cn); while(left(cn) != -1) cn = left(cn); } return ln; } // PRIVATE METHODS ============================================================================== /** * Creates a new id list and adds an id. * @param id id value * @param pos token position (only relevant for token index) */ private void addNewIds(final int id, final int pos) { byte[] vs = Num.newNum(id); if(tokenize) vs = Num.add(vs, pos); ids.add(vs); } /** * Appends an id to id list n. * @param id id value * @param pos token position (only relevant for token index) * @param n id list to append to */ private void addIds(final int id, final int pos, final int n) { byte[] vs = ids.get(n); vs = Num.add(vs, id); if(tokenize) vs = Num.add(vs, pos); ids.set(n, vs); } /** * Creates a new node. * @param key node key * @param id id value * @param pos token position (only relevant for token index) * @param par pointer to parent node * @param exist flag for reusing existing tree * @return pointer of the new node */ private int newNode(final byte[] key, final int id, final int pos, final int par, final boolean exist) { tree.add(-1); // left node tree.add(-1); // right node tree.add(par); // parent node mod.add(false); keys.add(key); addNewIds(id, pos); if(!exist) maps.put(Num.num(keys.size() - 1), ids.size() - 1); return mod.size() - 1; } /** * Gets the left child. * @param nd current node * @return left node */ private int left(final int nd) { return tree.get((nd << 1) + nd); } /** * Gets the right child. * @param nd current node * @return right node */ private int right(final int nd) { return tree.get((nd << 1) + nd + 1); } /** * Gets the parent node. * @param nd current node * @return parent node */ private int parent(final int nd) { return tree.get((nd << 1) + nd + 2); } /** * Sets the left child. * @param nd current node * @param val left node */ private void setLeft(final int nd, final int val) { tree.set((nd << 1) + nd, val); } /** * Sets the right child. * @param nd current node * @param val right node */ private void setRight(final int nd, final int val) { tree.set((nd << 1) + nd + 1, val); } /** * Sets the parent node. * @param nd current node * @param val parent node */ private void setParent(final int nd, final int val) { tree.set((nd << 1) + nd + 2, val); } /** * Adjusts the tree balance. * @param nd node to be adjusted */ private void adjust(final int nd) { int n = nd; mod.set(n, true); while(n != -1 && n != root && mod.get(parent(n))) { if(parent(n) == left(parent(parent(n)))) { final int y = right(parent(parent(n))); if(y != -1 && mod.get(y)) { mod.set(parent(n), false); mod.set(y, false); mod.set(parent(parent(n)), true); n = parent(parent(n)); } else { if(n == right(parent(n))) { n = parent(n); rotateLeft(n); } mod.set(parent(n), false); mod.set(parent(parent(n)), true); if(parent(parent(n)) != -1) rotateRight(parent(parent(n))); } } else { final int y = left(parent(parent(n))); if(y != -1 && mod.get(y)) { mod.set(parent(n), false); mod.set(y, false); mod.set(parent(parent(n)), true); n = parent(parent(n)); } else { if(n == left(parent(n))) { n = parent(n); rotateRight(n); } mod.set(parent(n), false); mod.set(parent(parent(n)), true); if(parent(parent(n)) != -1) rotateLeft(parent(parent(n))); } } } mod.set(root, false); } /** * Left rotation. * @param n node to be rotated */ private void rotateLeft(final int n) { final int r = right(n); setRight(n, left(r)); if(left(r) != -1) setParent(left(r), n); setParent(r, parent(n)); if(parent(n) == -1) root = r; else if(left(parent(n)) == n) setLeft(parent(n), r); else setRight(parent(n), r); setLeft(r, n); setParent(n, r); } /** * Right rotation. * @param n node to be rotated */ private void rotateRight(final int n) { final int l = left(n); setLeft(n, right(l)); if(right(l) != -1) setParent(right(l), n); setParent(l, parent(n)); if(parent(n) == -1) root = l; else if(right(parent(n)) == n) setRight(parent(n), l); else setLeft(parent(n), l); setRight(l, n); setParent(n, l); } @Override public String toString() { final TokenBuilder tb = new TokenBuilder(); tb.add("IndexTree[Root: ").addInt(root).add(Prop.NL); final int sz = keys.size(); for(int c = 0; c < sz; c++) { tb.add(" \"").add(keys.get(c)).add("\": ").add("ids"); if(tokenize) tb.add("/pos"); tb.add(": (").add(Num.toString(ids.get(c))).add(')'); final int left = tree.get(c * 3), right = tree.get(c * 3 + 1); if(left >= 0) tb.add(", left:").addInt(left); if(right >= 0) tb.add(", right:").addInt(right); tb.add(Prop.NL); } tb.add("]"); return tb.toString(); } }