package org.netbeans.gradle.project.properties; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.AbstractCollection; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.jtrim.utils.ExceptionHelper; public final class WeakValueHashMap<K, V> extends AbstractMap<K, V> { // Note that this is not a true general purpose implementation. // This class need to be rewritten if need to be used where performance matters, // also the view of this map (values() and entrySet()) are read-only and // inefficient. private final Map<K, TableRef<K, V>> wrappedMap; private final ReferenceQueue<V> references; public WeakValueHashMap() { this.wrappedMap = new HashMap<>(); this.references = new ReferenceQueue<>(); } private void removeUnreferenced() { while (true) { @SuppressWarnings("unchecked") TableRef<K, V> ref = (TableRef<K, V>)references.poll(); if (ref == null) { break; } wrappedMap.remove(ref.getKey()); } } private Map<K, TableRef<K, V>> getMap() { removeUnreferenced(); return wrappedMap; } @Override public int size() { if (wrappedMap.isEmpty()) { return 0; } return getMap().size(); } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean containsKey(Object key) { ExceptionHelper.checkNotNullArgument(key, "key"); return getMap().containsKey(key); } @Override public boolean containsValue(Object value) { ExceptionHelper.checkNotNullArgument(value, "value"); Map<K, TableRef<K, V>> map = getMap(); for (TableRef<?, V> valueRef: map.values()) { V currentValue = valueRef.getValue(); if (value.equals(currentValue)) { return true; } } return false; } @Override public V get(Object key) { ExceptionHelper.checkNotNullArgument(key, "key"); WeakReference<V> resultRef = getMap().get(key); return resultRef != null ? resultRef.get() : null; } @Override public V put(K key, V value) { ExceptionHelper.checkNotNullArgument(key, "key"); ExceptionHelper.checkNotNullArgument(value, "value"); WeakReference<V> resultRef = getMap().put(key, new TableRef<>(key, value, references)); return resultRef != null ? resultRef.get() : null; } @Override public V remove(Object key) { ExceptionHelper.checkNotNullArgument(key, "key"); WeakReference<V> resultRef = getMap().remove(key); return resultRef != null ? resultRef.get() : null; } @Override public void clear() { wrappedMap.clear(); removeUnreferenced(); } @Override public Set<K> keySet() { return getMap().keySet(); } @Override public Collection<V> values() { return new AbstractCollection<V>() { @Override public Iterator<V> iterator() { return new WeakRefItr<>(getMap().values().iterator()); } @Override public int size() { return WeakValueHashMap.this.size(); } }; } @Override public Set<Entry<K, V>> entrySet() { return new AbstractSet<Entry<K, V>>() { @Override public Iterator<Entry<K, V>> iterator() { return new EntryItr<>(getMap().entrySet().iterator()); } @Override public int size() { return WeakValueHashMap.this.size(); } }; } private static class EntryItr<K, V> implements Iterator<Entry<K, V>> { private final Iterator<Entry<K, TableRef<K, V>>> itr; private Entry<K, V> nextValue; public EntryItr(Iterator<Entry<K, TableRef<K, V>>> itr) { assert itr != null; this.itr = itr; moveToNext(); } private void moveToNext() { while (itr.hasNext()) { Entry<K, TableRef<K, V>> next = itr.next(); V nextEntryValue = next.getValue().getValue(); if (nextEntryValue != null) { nextValue = new AbstractMap.SimpleImmutableEntry<>( next.getKey(), nextEntryValue); return; } } nextValue = null; } @Override public boolean hasNext() { return nextValue != null; } @Override public Entry<K, V> next() { if (!hasNext()) { throw new NoSuchElementException(); } Entry<K, V> result = nextValue; moveToNext(); return result; } @Override public void remove() { throw new UnsupportedOperationException("The view of the entryset of this map is read-only."); } } private static class WeakRefItr<K, V> implements Iterator<V> { private final Iterator<TableRef<K, V>> itr; private V nextValue; public WeakRefItr(Iterator<TableRef<K, V>> itr) { assert itr != null; this.itr = itr; moveToNext(); } private void moveToNext() { while (itr.hasNext()) { TableRef<?, V> next = itr.next(); nextValue = next.getValue(); if (nextValue != null) { return; } } nextValue = null; } @Override public boolean hasNext() { return nextValue != null; } @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } V result = nextValue; moveToNext(); return result; } @Override public void remove() { throw new UnsupportedOperationException("The view of the values of this map is read-only."); } } private static class TableRef<K, V> extends WeakReference<V> { private final K key; public TableRef(K key, V value, ReferenceQueue<V> queue) { super(value, queue); this.key = key; } public K getKey() { return key; } public V getValue() { return super.get(); } } }