package org.mockserver.collections; import org.mockserver.model.ObjectWithReflectiveEqualsHashCodeToString; import java.util.*; /** * @author jamesdbloom */ public class CircularMultiMap<K, V> implements Map<K, V> { private final int maxValuesPerKeySize; private final Map<K, List<V>> backingMap; public CircularMultiMap(int maxNumberOfKeys, int maxNumberOfValuesPerKey) { this.maxValuesPerKeySize = maxNumberOfValuesPerKey; backingMap = Collections.synchronizedMap(new CircularHashMap<K, List<V>>(maxNumberOfKeys)); } @Override public synchronized int size() { return backingMap.size(); } @Override public synchronized boolean isEmpty() { return backingMap.isEmpty(); } @Override public synchronized boolean containsKey(Object key) { return backingMap.containsKey(key); } @Override public synchronized boolean containsValue(Object value) { for (Entry<K, List<V>> entry : backingMap.entrySet()) { if (entry.getValue().contains(value)) { return true; } } return false; } @Override public synchronized V get(Object key) { List<V> values = backingMap.get(key); if (values != null && values.size() > 0) { return values.get(0); } else { return null; } } public synchronized List<V> getAll(Object key) { List<V> values = backingMap.get(key); if (values == null) { values = new ArrayList<V>(); } return values; } public synchronized List<K> getKeyListForValues() { List<K> values = new ArrayList<K>(); for (K key : backingMap.keySet()) { for (V value : backingMap.get(key)) { values.add(key); } } return values; } @Override public synchronized V put(K key, V value) { if (containsKey(key)) { backingMap.get(key).add(value); } else { List<V> list = Collections.synchronizedList(new CircularLinkedList<V>(maxValuesPerKeySize)); list.add(value); backingMap.put(key, list); } return value; } @Override public synchronized V remove(Object key) { List<V> values = backingMap.get(key); if (values != null && values.size() > 0) { return values.remove(0); } else { return null; } } public synchronized List<V> removeAll(Object key) { return backingMap.remove(key); } @Override public synchronized void putAll(Map<? extends K, ? extends V> map) { for (Entry<? extends K, ? extends V> entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public synchronized void clear() { backingMap.clear(); } @Override public synchronized Set<K> keySet() { return backingMap.keySet(); } @Override public synchronized Collection<V> values() { Collection<V> values = new ArrayList<V>(); for (List<V> valuesForKey : backingMap.values()) { values.addAll(valuesForKey); } return values; } @Override public synchronized Set<Entry<K, V>> entrySet() { Set<Entry<K, V>> entrySet = new LinkedHashSet<Entry<K, V>>(); for (Entry<K, List<V>> entry : backingMap.entrySet()) { for (V value : entry.getValue()) { entrySet.add(new ImmutableEntry(entry.getKey(), value)); } } return entrySet; } class ImmutableEntry extends ObjectWithReflectiveEqualsHashCodeToString implements Entry<K, V> { private final K key; private final V value; ImmutableEntry(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public V setValue(V value) { throw new UnsupportedOperationException("ImmutableEntry is immutable"); } } }