package edu.stanford.nlp.util; import java.io.IOException; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.Set; import java.util.Spliterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; /** * A hash set supporting full concurrency of retrievals and * high expected concurrency for updates but with an (adjustable) maximum size. * The maximum only prevents further add operations. It doesn't stop the maximum * being exceeded when first loaded or via an addAll(). This is deliberate! * * @author Christopher Manning * @param <E> the type of elements maintained by this set */ public class MaxSizeConcurrentHashSet<E> implements Set<E>, Serializable { private final ConcurrentMap<E, Boolean> m; private transient Set<E> s; // the keySet of the Map private int maxSize; /** Create a ConcurrentHashSet with no maximum size. */ public MaxSizeConcurrentHashSet() { this(-1); } /** Create a ConcurrentHashSet with the maximum size given. */ public MaxSizeConcurrentHashSet(int maxSize) { this.m = new ConcurrentHashMap<>(); this.maxSize = maxSize; init(); } /** Create a ConcurrentHashSet with the elements in s. * This set has no maximum size. */ public MaxSizeConcurrentHashSet(Set<? extends E> s) { this.m = new ConcurrentHashMap<>(Math.max(s.size(), 16)); init(); addAll(s); this.maxSize = -1; } private void init() { this.s = m.keySet(); } public int getMaxSize() { return maxSize; } public void setMaxSize(int maxSize) { this.maxSize = maxSize; } /** Adds the element if the set is not already full. Otherwise, silently * doesn't add it. * * @param e The element * @return true iff the element was added. This is slightly different from the semantics * of a normal Set which returns true if the item didn't used to be there and was added. * Here it only returns true if it was added. */ @Override public boolean add(E e) { synchronized(this) { if (maxSize >= 0 && size() >= maxSize) { // can't put new value return false; } else { return m.put(e, Boolean.TRUE) == null; } } } @Override public void clear() { m.clear(); } @Override public int size() { return m.size(); } @Override public boolean isEmpty() { return m.isEmpty(); } @Override public boolean contains(Object o) { return m.containsKey(o); } @Override public boolean remove(Object o) { return m.remove(o) != null; } @Override public Iterator<E> iterator() { return s.iterator(); } @Override public Object[] toArray() { return s.toArray(); } @Override public <T> T[] toArray(T[] a) { return s.toArray(a); } @Override public String toString() { return s.toString(); } @Override public int hashCode() { return s.hashCode(); } @Override public boolean equals(Object o) { return s.equals(o); } @Override public boolean containsAll(Collection<?> c) {return s.containsAll(c);} @Override public boolean removeAll(Collection<?> c) {return s.removeAll(c);} @Override public boolean retainAll(Collection<?> c) {return s.retainAll(c);} /** Add all the items. * This doesn't use the add method, because we want to bypass the limit here. */ @Override public boolean addAll(Collection<? extends E> c) { boolean added = false; for (E item : c) { if (m.put(item, Boolean.TRUE) == null) { added = true; } } return added; } // Override default methods in Collection @Override public void forEach(Consumer<? super E> action) { s.forEach(action);} @Override public boolean removeIf(Predicate<? super E> filter) { return s.removeIf(filter);} @Override public Spliterator<E> spliterator() {return s.spliterator();} @Override public Stream<E> stream() {return s.stream();} @Override public Stream<E> parallelStream() {return s.parallelStream();} private static final long serialVersionUID = 1L; private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); init(); } }