package edu.stanford.nlp.util.concurrent; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.AbstractCollection; import java.util.AbstractList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.RandomAccess; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import edu.stanford.nlp.io.IOUtils; import edu.stanford.nlp.util.Generics; import edu.stanford.nlp.util.Index; /** * A fast threadsafe index that supports constant-time lookup in both directions. This * index is tuned for circumstances in which readers significantly outnumber writers. * * @author Spence Green * * @param <E> */ public class ConcurrentHashIndex<E> extends AbstractCollection<E> implements Index<E>, RandomAccess { private static final long serialVersionUID = 6465313844985269109L; public static final int UNKNOWN_ID = -1; private static final int DEFAULT_INITIAL_CAPACITY = 100; private final ConcurrentHashMap<E,Integer> item2Index; private int indexSize; private final ReentrantLock lock; private final AtomicReference<Object[]> index2Item; /** * Constructor. */ public ConcurrentHashIndex() { this(DEFAULT_INITIAL_CAPACITY); } /** * Constructor. * * @param initialCapacity */ public ConcurrentHashIndex(int initialCapacity) { item2Index = new ConcurrentHashMap<>(initialCapacity); indexSize = 0; lock = new ReentrantLock(); Object[] arr = new Object[initialCapacity]; index2Item = new AtomicReference<>(arr); } @SuppressWarnings("unchecked") @Override public E get(int i) { Object[] arr = index2Item.get(); if (i < indexSize) { // arr.length guaranteed to be == to size() given the // implementation of indexOf below. return (E) arr[i]; } throw new ArrayIndexOutOfBoundsException(String.format("Out of bounds: %d >= %d", i, indexSize)); } @Override public int indexOf(E o) { Integer id = item2Index.get(o); return id == null ? UNKNOWN_ID : id; } @Override public int addToIndex(E o) { Integer index = item2Index.get(o); if (index != null) { return index; } lock.lock(); try { // Recheck state if (item2Index.containsKey(o)) { return item2Index.get(o); } else { final int newIndex = indexSize++; Object[] arr = index2Item.get(); assert newIndex <= arr.length; if (newIndex == arr.length) { // Increase size of array if necessary Object[] newArr = new Object[2*newIndex]; System.arraycopy(arr, 0, newArr, 0, arr.length); arr = newArr; } arr[newIndex] = o; index2Item.set(arr); item2Index.put(o, newIndex); return newIndex; } } finally { lock.unlock(); } } @Override @Deprecated public int indexOf(E o, boolean add) { if (add) { return addToIndex(o); } else { return indexOf(o); } } @Override public boolean add(E o) { return addToIndex(o) != UNKNOWN_ID; } @Override public boolean addAll(Collection<? extends E> c) { boolean changed = false; for (E element: c) { changed |= add(element); } return changed; } @Override public List<E> objectsList() { return Generics.newArrayList(item2Index.keySet()); } @Override public Collection<E> objects(final int[] indices) { return new AbstractList<E>() { @Override public E get(int index) { return ConcurrentHashIndex.this.get(indices[index]); } @Override public int size() { return indices.length; } }; } @Override public boolean isLocked() { return false; } @Override public void lock() { throw new UnsupportedOperationException(); } @Override public void unlock() { throw new UnsupportedOperationException(); } @Override public void saveToWriter(Writer out) throws IOException { final String nl = System.getProperty("line.separator"); for (int i = 0, sz = indexSize; i < sz; i++) { E o = get(i); if (o != null) { out.write(i + "=" + get(i) + nl); } } } @Override public void saveToFilename(String s) { PrintWriter bw = null; try { bw = IOUtils.getPrintWriter(s); for (int i = 0, size = indexSize; i < size; i++) { E o = get(i); if (o != null) { bw.printf("%d=%s%n", i, o.toString()); } } bw.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bw != null) { bw.close(); } } } @Override public Iterator<E> iterator() { return new Iterator<E>() { private int index = 0; private int size = ConcurrentHashIndex.this.size(); @Override public boolean hasNext() { return index < size; } @Override public E next() { return ConcurrentHashIndex.this.get(index++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { return indexSize; } @Override public String toString() { StringBuilder buff = new StringBuilder("["); int i; final int size = size(); for (i = 0; i < size; i++) { E e = get(i); if (e != null) { buff.append(i).append('=').append(e); if (i < (size-1)) buff.append(','); } } if (i < size()) buff.append("..."); buff.append(']'); return buff.toString(); } @SuppressWarnings("unchecked") @Override public boolean contains(Object o) { return indexOf((E) o) != UNKNOWN_ID; } @Override public void clear() { lock.lock(); try { item2Index.clear(); indexSize = 0; Object[] arr = new Object[DEFAULT_INITIAL_CAPACITY]; index2Item.set(arr); } finally { lock.unlock(); } } }