package org.basex.util.hash; import static org.basex.util.Token.*; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import org.basex.io.in.DataInput; import org.basex.io.out.DataOutput; import org.basex.util.TokenBuilder; import org.basex.util.Util; /** * This is an efficient hash set, storing keys in byte arrays. * The {@link TokenMap} class extends it to a hash map. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public class TokenSet implements Iterable<byte[]> { /** Initial hash capacity. */ protected static final int CAP = 1 << 3; /** Hash entries. Note: actual number of entries is {@code size - 1}. */ protected int size = 1; /** Hashed keys. */ protected byte[][] keys; /** Pointers to the next token. */ private int[] next; /** Hash table buckets. */ private int[] bucket; /** * Constructor. */ public TokenSet() { keys = new byte[CAP][]; next = new int[CAP]; bucket = new int[CAP]; } /** * Constructor. * @param init initial tokens */ public TokenSet(final byte[]... init) { this(); for(final byte[] i : init) add(i); } /** * Constructor. * @param in input stream * @throws IOException I/O exception */ public TokenSet(final DataInput in) throws IOException { read(in); } /** * Reads the token set from the specified input. * @param in input stream * @throws IOException I/O exception */ protected void read(final DataInput in) throws IOException { keys = in.readTokens(); next = in.readNums(); bucket = in.readNums(); size = in.readNum(); } /** * Writes the token set to the specified output. * @param out output stream * @throws IOException I/O exception */ public void write(final DataOutput out) throws IOException { out.writeTokens(keys); out.writeNums(next); out.writeNums(bucket); out.writeNum(size); } /** * Indexes the specified key and returns the offset of the added key. * If the key already exists, a negative offset is returned. * @param key key * @return offset of added key, negative offset otherwise */ public final int add(final byte[] key) { if(size == next.length) rehash(); final int p = hash(key) & bucket.length - 1; for(int id = bucket[p]; id != 0; id = next[id]) { if(eq(key, keys[id])) return -id; } next[size] = bucket[p]; keys[size] = key; bucket[p] = size; return size++; } /** * Deletes the specified key. * <b>Warning</b>: After a deletion, the key array will have {@code null} * entries, and the total number of entries will not reflect the number * of valid entries anymore. * @param key key * @return deleted key or 0 */ public int delete(final byte[] key) { final int p = hash(key) & bucket.length - 1; int o = 0, n; for(int id = bucket[p]; id != 0; id = n) { n = next[id]; if(eq(key, keys[id])) { if(bucket[p] == id) bucket[p] = n; else next[o] = next[n]; keys[id] = null; return id; } o = id; } return 0; } /** * Returns the id of the specified key or 0 if the key does not exist. * @param key key to be found * @return id or 0 if nothing was found */ public final int id(final byte[] key) { final int p = hash(key) & bucket.length - 1; for(int id = bucket[p]; id != 0; id = next[id]) { if(eq(key, keys[id])) return id; } return 0; } /** * Returns the specified key. * @param i key index * @return key */ public final byte[] key(final int i) { return keys[i]; } /** * Returns the hash keys. * @return keys */ public final byte[][] keys() { final byte[][] tmp = new byte[size()][]; System.arraycopy(keys, 1, tmp, 0, size - 1); return tmp; } /** * Returns the number of entries. * @return number of entries */ public final int size() { return size - 1; } /** * Resizes the hash table. */ protected void rehash() { final int s = size << 1; final int[] tmp = new int[s]; final int l = bucket.length; for(int i = 0; i != l; ++i) { int id = bucket[i]; while(id != 0) { final int p = hash(keys[id]) & s - 1; final int nx = next[id]; next[id] = tmp[p]; tmp[p] = id; id = nx; } } bucket = tmp; next = Arrays.copyOf(next, s); final byte[][] k = new byte[s][]; System.arraycopy(keys, 0, k, 0, size); keys = k; } @Override public final Iterator<byte[]> iterator() { return new Iterator<byte[]>() { private int c = 1; @Override public boolean hasNext() { return c < size; } @Override public byte[] next() { return keys[c++]; } @Override public void remove() { Util.notexpected(); } }; } @Override public String toString() { return new TokenBuilder(Util.name(this)).add('[').addSep(keys(), ", ").add(']').toString(); } }