/* * This file is part of the HyperGraphDB source distribution. This is copyrighted * software. For permitted uses, licensing options and redistribution, please see * the LicensingInformation file at the root level of the distribution. * * Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved. */ package org.hypergraphdb.util; import java.lang.reflect.Array; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.hypergraphdb.HGRandomAccessResult; /** * * <p> * An implementation <code>SortedSet</code> based on primitive arrays that grow * as needed without ever shrinking. Lookup is log(n), but insertion and removal * take longer obviously so this implementation is mainly suitable for small sets * of a few dozen elements, or for sets that are searched/iterated much more * frequently than they are changed. The main reason for being of this implementation * is space efficiency: a red-black tree holds additional 12 bytes per datum. So while * for a large set, a tree should be used, the array-based implementation is preferable * for many small sets like many of HyperGraphDB's incidence sets. * </p> * * <p> * Some benchmarking experiments comparing this (rather simple) implementation to red-black * trees (both LLRBTree and the standard Java TreeSet): working with about up to 10000 elements, * insertion and removal have a comparable performance, with the array-based implementation * being about 15% slower (elements inserted/removed in random order). The LLRBTree implementation * is actually noticeably slower than TreeSet, probably due to recursion. However, in "read-mode", * when iterating over the set, using it as a HGRandomAccessResult, the array-based implementation * is 8-10 faster. Understandable since here moving to the next element amounts to incrementing an integer * while in a tree a lookup for the successor must be performed (e.g. min(parent(current))). So for * set of this order of magnitude and/or sets that are being read more than they are modified, * it is strongly advisable to use the ArrayBasedSet. * </p> * * @author Borislav Iordanov * * @param <E> */ @SuppressWarnings("unchecked") public class ArrayBasedSet<E> implements HGSortedSet<E>, CloneMe { Class<E> type; E [] array; Comparator<E> comparator = null; int size = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); int lookup(E key) { int low = 0; int high = size-1; Comparable<E> ckey = comparator == null ? (Comparable<E>)key : null; while (low <= high) { int mid = (low + high) >> 1; E midVal = array[mid]; int cmp = ckey == null ? comparator.compare(midVal, key) : -ckey.compareTo(midVal); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. } /** * <p> * Initialize from the given array. * </p> * * @param A The array used to initialize. An internal array is created * with the same size as <code>A</code> and all its elements are copied. */ public ArrayBasedSet(E [] A) { this(A, null); } /** * <p> * Initialize an empty set with a given initial capacity. * </p> * * @param A The array used to initialize. An internal array is created * with the same type as as <code>A</code> and with <code>size</code> number * of slots. */ public ArrayBasedSet(E [] A, int capacity) { this(A, capacity, null); } /** * <p> * Initialize an empty set with a given initial capacity, and a given * comparator. * </p> * * @param A The array used to initialize. An internal array is created * with the same type as as <code>A</code> and with <code>size</code> number * of slots. * @param Comparator The comparator used to compare elements. */ public ArrayBasedSet(E [] A, int capacity, Comparator<E> comparator) { type = (Class<E>)A.getClass().getComponentType(); array = (E[])java.lang.reflect.Array.newInstance(type, capacity); size = 0; this.comparator = comparator; } /** * <p> * Initialize from the given array and with the given <code>Comparator</code>. * </p> * * @param A The array used to initialize. An internal array is created * with the same size as <code>A</code> and all its elements are copied. * @param Comparator The comparator used to compare elements. */ public ArrayBasedSet(E [] A, Comparator<E> comparator) { this.comparator = comparator; type = (Class<E>)A.getClass().getComponentType(); array = (E[])java.lang.reflect.Array.newInstance(type, A.length); System.arraycopy(A, 0, array, 0, A.length); size = A.length; } public void setFromArray(E [] A) { lock.writeLock().lock(); try { if (array.length < A.length) array = (E[])java.lang.reflect.Array.newInstance(type, A.length); System.arraycopy(A, 0, array, 0, A.length); this.size = A.length; } finally { lock.writeLock().unlock(); } } public ReadWriteLock getLock() { return lock; } public void setLock(ReadWriteLock lock) { if (lock == null) throw new NullPointerException("ArrayBasedSet.lock can't be null."); this.lock = lock; } public Comparator<? super E> comparator() { return comparator; } public E getAt(int i) { lock.readLock().lock(); try { if (i < 0 || i > size()) throw new IllegalArgumentException("index " + i + " out of bounds [0," + size() + ")."); else return array[i]; } finally { lock.readLock().unlock(); } } public E removeAt(int i) { lock.writeLock().lock(); try { if (i < 0 || i > size()) throw new IllegalArgumentException("index " + i + " out of bounds [0," + size() + ")."); else { E result = array[i]; if (i < size - 1) // if it's not the last element System.arraycopy(array, i + 1, array, i, size - i - 1); size--; return result; } } finally { lock.writeLock().unlock(); } } public E first() { lock.readLock().lock(); try { if (size == 0) throw new NoSuchElementException(); return array[0]; } finally { lock.readLock().unlock(); } } public SortedSet<E> headSet(E toElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public E last() { lock.readLock().lock(); try { if (size == 0) throw new NoSuchElementException(); return array[size-1]; } finally { lock.readLock().unlock(); } } public SortedSet<E> subSet(E fromElement, E toElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public SortedSet<E> tailSet(E fromElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public boolean add(E o) { lock.writeLock().lock(); try { int idx = lookup((E)o); if (idx >= 0) return false; else idx = -(idx + 1); if (size < array.length) { System.arraycopy(array, idx, array, idx + 1, size - idx); array[idx] = o; } else // need to grow the array... { E [] tmp = (E[])java.lang.reflect.Array.newInstance(type, (int)(1.5 * size) + 1); System.arraycopy(array, 0, tmp, 0, idx); tmp[idx] = o; System.arraycopy(array, idx, tmp, idx + 1, size - idx); array = tmp; } size++; return true; } finally { lock.writeLock().unlock(); } } public boolean addAll(Collection<? extends E> c) { boolean modified = false; for (Object x : c) if (add((E)x)) modified = true; return modified; } public void clear() { lock.writeLock().lock(); size = 0; lock.writeLock().unlock(); } public boolean contains(Object o) { lock.readLock().lock(); try { return lookup((E)o) >= 0; } finally { lock.readLock().unlock(); } } public boolean containsAll(Collection<?> c) { for (Object x : c) if (!contains(x)) return false; return true; } public boolean isEmpty() { lock.readLock().lock(); try { return size == 0; } finally { lock.readLock().unlock(); } } public Iterator<E> iterator() { return new ResultSet(false); } public HGRandomAccessResult<E> getSearchResult() { return new ResultSet(true); } public boolean remove(Object o) { lock.writeLock().lock(); try { int idx = lookup((E)o); if (idx < 0) return false; else if (idx < size - 1) // if it's not the last element System.arraycopy(array, idx + 1, array, idx, size - idx - 1); size--; return true; } finally { lock.writeLock().unlock(); } } public boolean removeAll(Collection<?> c) { boolean modified = false; for (Object x : c) if (remove((E)x)) modified = true; return modified; } public boolean retainAll(Collection<?> c) { lock.writeLock().lock(); try { boolean modified = false; for (int i = 0; i < size; i++) if (!c.contains(array[i])) { System.arraycopy(array, i + 1, array, i, size - i); size--; modified = true; } return modified; } finally { lock.writeLock().unlock(); } } public int size() { lock.readLock().lock(); try { return size; } finally { lock.readLock().unlock(); } } public Object[] toArray() { lock.readLock().lock(); try { Object [] A = new Object[array.length]; System.arraycopy(array, 0, A, 0, array.length); return A; } finally { lock.readLock().unlock(); } } public ArrayBasedSet<E> duplicate() { try { ArrayBasedSet<E> S = (ArrayBasedSet<E>)super.clone(); S.array = ((Object)array.getClass() == (Object)Object[].class) ? (E[]) new Object[size] : (E[]) Array.newInstance(array.getClass().getComponentType(), size); System.arraycopy(array, 0, S.array, 0, size); return S; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } } public <T> T[] toArray(T[] a) { lock.readLock().lock(); try { if (a.length < size) { Class<? extends T[]> type = (Class<? extends T[]>)a.getClass(); T[] copy = ((Object)type == (Object)Object[].class) ? (T[]) new Object[size] : (T[]) Array.newInstance(type.getComponentType(), size); System.arraycopy(array, 0, copy, 0, Math.min(array.length, size)); return copy; } System.arraycopy(array, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } finally { lock.readLock().unlock(); } } class ResultSet implements HGRandomAccessResult<E> { int pos = -1; boolean locked; ResultSet(boolean locked) { this.locked = locked; if (locked) lock.readLock().lock(); } public GotoResult goTo(E value, boolean exactMatch) { int idx = lookup(value); if (idx >= 0) { pos = idx; return GotoResult.found; } else if (exactMatch) return GotoResult.nothing; else { idx = -(idx + 1); if (idx >= size) return GotoResult.nothing; pos = idx; return GotoResult.close; } } public void goBeforeFirst() { pos = -1; } public void goAfterLast() { pos = size; } public boolean hasPrev() { return pos > 0; } public E prev() { return array[--pos]; } public boolean hasNext() { return pos + 1 < size; } public E next() { return array[++pos]; } public void remove() { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public void close() { if (locked) { lock.readLock().unlock(); locked = false; } } public E current() { if (pos < 0 || pos >= size) throw new NoSuchElementException(); return array[pos]; } public boolean isOrdered() { return true; } } }