package org.basex.index; import static org.basex.util.Token.*; import java.lang.ref.*; import java.util.concurrent.locks.*; import org.basex.util.*; /** * This class caches sizes and offsets from index results. * * @author BaseX Team 2005-17, BSD License * @author Dimitar Popov */ public final class IndexCache { /** Queue used to collect unused keys. */ private final ReferenceQueue<IndexEntry> queue = new ReferenceQueue<>(); /** Read-write lock. */ private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true); /** Hash table buckets. */ private BucketEntry[] buckets = new BucketEntry[Array.CAPACITY]; /** Number of entries in the cache. */ private int size; /** * Gets cached entry for the specified key. * @param key key * @return cached entry or {@code null} if the entry is stale */ public IndexEntry get(final byte[] key) { final int hash = hash(key); rwl.readLock().lock(); try { final int i = indexFor(hash, buckets.length); BucketEntry e = buckets[i]; while(e != null) { final IndexEntry entry = e.get(); if(entry != null && e.hash == hash && eq(entry.key, key)) return entry; e = e.next; } } finally { rwl.readLock().unlock(); } return null; } /** * Adds a new cache entry. If an entry with the specified key already exists, * it will be updated. * @param key key * @param count number of index hits * @param offset offset to id list * @return cache entry */ public IndexEntry add(final byte[] key, final int count, final long offset) { final int hash = hash(key); rwl.writeLock().lock(); try { purge(); final int i = indexFor(hash, buckets.length); BucketEntry current = buckets[i], prev = current; while(current != null) { final BucketEntry next = current.next; final IndexEntry entry = current.get(); if(entry == null) { delete(i, current, prev, next); } else if(current.hash == hash && eq(entry.key, key)) { update(entry, count, offset); return entry; } prev = current; current = next; } final IndexEntry entry = new IndexEntry(key, count, offset); add(i, hash, entry); return entry; } finally { rwl.writeLock().unlock(); } } /** * Deletes a cached entry. * @param key key */ public void delete(final byte[] key) { final int hash = hash(key); rwl.writeLock().lock(); try { purge(); final int i = indexFor(hash, buckets.length); BucketEntry e = buckets[i], prev = e; while(e != null) { final BucketEntry next = e.next; final IndexEntry entry = e.get(); if(entry == null) { delete(i, e, prev, next); } else if(e.hash == hash && eq(entry.key, key)) { delete(i, e, prev, next); break; } prev = e; e = next; } } finally { rwl.writeLock().unlock(); } } /** * Purges stale entries from the cache. */ private void purge() { for(Object x; (x = queue.poll()) != null;) { final BucketEntry e = (BucketEntry) x; final int i = indexFor(e.hash, buckets.length); BucketEntry prev = buckets[i], p = prev; while(p != null) { final BucketEntry next = p.next; if(p == e) { delete(i, e, prev, next); break; } prev = p; p = next; } } } /** * Add a new index entry to the bucket with the specified index. * @param i bucket index * @param hash hash of the new index key * @param entry index entry */ private void add(final int i, final int hash, final IndexEntry entry) { buckets[i] = new BucketEntry(hash, buckets[i], entry, queue); if(++size == buckets.length) rehash(); } /** * Update an existing index entry. * @param entry index entry to update * @param size new size * @param offset new offset */ private static void update(final IndexEntry entry, final int size, final long offset) { entry.size = size; entry.offset = offset; } /** * Deletes a cached entry from the buckets with the specified index. * @param i buckets index * @param e cached entry to delete * @param p previous cache entry * @param n next cache entry */ private void delete(final int i, final BucketEntry e, final BucketEntry p, final BucketEntry n) { if(p == e) buckets[i] = n; else p.next = n; e.next = null; --size; } /** * Resizes the hash table. */ private void rehash() { purge(); final int s = size << 1; final BucketEntry[] tmp = new BucketEntry[s]; final int l = buckets.length; for(int i = 0; i < l; ++i) { BucketEntry e = buckets[i]; buckets[i] = null; while(e != null) { final BucketEntry next = e.next; final int p = indexFor(e.hash, tmp.length); e.next = tmp[p]; tmp[p] = e; e = next; } } buckets = tmp; } /** * Returns buckets index for a hash code. * @param h hash code * @param n number of available buckets * @return index of a buckets */ private static int indexFor(final int h, final int n) { return h & n - 1; } /** * Cache buckets entry. Used to implement a linked list of cache entries for * each buckets. It also stores the hash of the current entry for better * performance. */ private static class BucketEntry extends SoftReference<IndexEntry> { /** Hash code of the stored cache entry key. */ final int hash; /** Next buckets entry or {@code null} if the last one for this buckets. */ BucketEntry next; /** * Constructor. * @param h hash code of the cache entry key * @param n next buckets entry or {@code null} if the last one * @param v stored cache entry * @param rq reference queue */ BucketEntry(final int h, final BucketEntry n, final IndexEntry v, final ReferenceQueue<IndexEntry> rq) { super(v, rq); hash = h; next = n; } } }