package edu.harvard.mcb.leschziner.storage.localstorage; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.EntryListener; import com.hazelcast.core.MultiMap; public class HashMultiMap<K, V> implements MultiMap<K, V> { private final Map<K, Collection<V>> multimap; private final Collection<EntryListener<K, V>> globalListeners; private final Map<K, Collection<EntryListener<K, V>>> specificListeners; private final String name; public HashMultiMap(String name) { multimap = new ConcurrentHashMap<>(); globalListeners = new ConcurrentLinkedQueue<>(); specificListeners = new ConcurrentHashMap<>(); this.name = name; } @Override public void destroy() { this.clear(); } @Override public Object getId() { return hashCode(); } @Override public InstanceType getInstanceType() { return InstanceType.MULTIMAP; } @Override public void addEntryListener(EntryListener<K, V> listener, boolean arg1) { globalListeners.add(listener); } @Override public void addEntryListener(EntryListener<K, V> listener, K key, boolean arg2) { if (specificListeners.containsKey(key)) { specificListeners.get(key).add(listener); } else { Collection<EntryListener<K, V>> keyListeners = new ConcurrentLinkedQueue<>(); keyListeners.add(listener); specificListeners.put(key, keyListeners); } } @Override public void addLocalEntryListener(EntryListener<K, V> listener) { globalListeners.add(listener); } @Override public void clear() { multimap.clear(); globalListeners.clear(); specificListeners.clear(); } @Override public boolean containsEntry(K key, V value) { return multimap.containsKey(key) && multimap.get(key).contains(value); } @Override public boolean containsKey(K key) { return multimap.containsKey(key); } @Override public boolean containsValue(Object arg0) { for (Collection<V> values : multimap.values()) { if (values.contains(arg0)) { return true; } } return false; } @Override public Set<java.util.Map.Entry<K, V>> entrySet() { HashSet<java.util.Map.Entry<K, V>> entrySet = new HashSet<>(); // For each key for (K key : multimap.keySet()) { // Associate key with each value for (V value : multimap.get(key)) { entrySet.add(new Entry<K, V>(key, value)); } } return entrySet; } @Override public Collection<V> get(K key) { return multimap.get(key); } @Override public String getName() { return name; } @Override public Set<K> keySet() { return multimap.keySet(); } @Override public Set<K> localKeySet() { return multimap.keySet(); } @Override public void lock(K arg0) { // Using concurrent datastructures, this is not necessary } @Override public boolean lockMap(long arg0, TimeUnit arg1) { // Using concurrent datastructures, this is not necessary return true; } @Override public boolean put(K key, V value) { if (multimap.containsKey(key) && multimap.get(key).add(value)) { notifyListenersOfAdd(key, value); } else { ConcurrentLinkedQueue<V> list = new ConcurrentLinkedQueue<>(); list.add(value); multimap.put(key, list); notifyListenersOfAdd(key, value); } return true; } @Override public Collection<V> remove(Object key) { Collection<V> values = multimap.remove(key); // Notify listeners notifyListenersOfRemove((K) key, null); return values; } @Override public boolean remove(Object key, Object value) { if (multimap.containsKey(key) && multimap.get(key).remove(value)) { // Notify Listeners notifyListenersOfRemove((K) key, (V) value); return true; } return false; } @Override public void removeEntryListener(EntryListener<K, V> arg0) { globalListeners.remove(arg0); } @Override public void removeEntryListener(EntryListener<K, V> listener, K key) { if (specificListeners.containsKey(key)) specificListeners.get(key).remove(listener); } @Override public int size() { return multimap.size(); } @Override public boolean tryLock(K arg0) { // Using concurrent datastructures, this is not necessary return true; } @Override public boolean tryLock(K arg0, long arg1, TimeUnit arg2) { // Using concurrent datastructures, this is not necessary return true; } @Override public void unlock(K arg0) { // Using concurrent datastructures, this is not necessary } @Override public void unlockMap() { // Using concurrent datastructures, this is not necessary } @Override public int valueCount(K arg0) { int count = 0; for (Collection<V> keyValues : multimap.values()) { count += keyValues.size(); } return count; } @Override public Collection<V> values() { List<V> values = new LinkedList<>(); for (Collection<V> keyValues : multimap.values()) { values.addAll(keyValues); } return values(); } private void notifyListenersOfAdd(K key, V value) { // Notify All EntryEvent<K, V> event = new EntryEvent<K, V>("m:" + name, null, EntryEvent.TYPE_ADDED, key, value); for (EntryListener<K, V> listener : globalListeners) { listener.entryAdded(event); } if (specificListeners.containsKey(key)) for (EntryListener<K, V> listener : specificListeners.get(key)) { listener.entryAdded(event); } } private void notifyListenersOfRemove(K key, V value) { // Notify All EntryEvent<K, V> event = new EntryEvent<K, V>("m:" + name, null, EntryEvent.TYPE_REMOVED, key, value); for (EntryListener<K, V> listener : globalListeners) { listener.entryRemoved(event); } // Notify Specific if (specificListeners.containsKey(key)) for (EntryListener<K, V> listener : specificListeners.get(key)) { listener.entryRemoved(event); } } static class Entry<K, V> implements java.util.Map.Entry<K, V> { final K key; V value; /** * Creates new entry. */ Entry(K k, V v) { value = v; key = k; } @Override public final K getKey() { return key; } @Override public final V getValue() { return value; } @Override public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } } }