blob: e57f92dce0e0252d26b007a7a6bceb61697bcf53 [file] [log] [blame]
/*
* Copyright (c) 2004 - 2012 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.util.ref;
import org.eclipse.net4j.util.collection.MapEntry;
import java.lang.ref.ReferenceQueue;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* A {@link Map} implementation that uses {@link KeyedReference} instances ({@link KeyedStrongReference},
* {@link KeyedSoftReference}, {@link KeyedWeakReference} or {@link KeyedPhantomReference}) as its values.
* <p>
* A <code>ReferenceValueMap</code> can be used to cache mappings until the <em>value</em> of the mapping is no longer
* reachable from outside of the map
* <p>
* <b>Note:</b> This map is not synchronized. If it is to be used by multiple threads concurrently the user is
* responsible for applying proper external synchronization!
*
* @author Eike Stepper
* @since 3.3
*/
public abstract class ReferenceValueMap2<K, V> extends AbstractMap<K, V>
{
Map<K, KeyedReference<K, V>> map;
ReferenceQueue<V> queue;
private EntrySet entrySet;
public ReferenceValueMap2()
{
this(new HashMap<K, KeyedReference<K, V>>());
}
public ReferenceValueMap2(Map<K, KeyedReference<K, V>> map)
{
if (!map.isEmpty())
{
throw new IllegalArgumentException("!map.isEmpty()"); //$NON-NLS-1$
}
this.map = map;
queue = createQueue();
}
@Override
public int size()
{
purgeQueue();
return map.size();
}
@Override
public boolean isEmpty()
{
purgeQueue();
return map.isEmpty();
}
@Override
public boolean containsKey(Object key)
{
KeyedReference<K, V> ref = map.get(key);
if (ref != null)
{
if (ref.get() == null)
{
// ref.enqueue();
return false;
}
return true;
}
return false;
}
@Override
public boolean containsValue(Object value)
{
if (value == null)
{
throw new IllegalArgumentException("value == null"); //$NON-NLS-1$
}
for (KeyedReference<K, V> ref : map.values())
{
V v = ref.get();
if (v == null)
{
// ref.enqueue();
return false;
}
if (value.equals(v))
{
return true;
}
}
return false;
}
@Override
public V get(Object key)
{
KeyedReference<K, V> ref = map.get(key);
return dereference(ref);
}
@Override
public V put(K key, V value)
{
try
{
KeyedReference<K, V> ref = createReference(key, value, queue);
KeyedReference<K, V> oldRef = map.put(key, ref);
return dereference(oldRef);
}
finally
{
purgeQueue();
}
}
@Override
public V remove(Object key)
{
KeyedReference<K, V> ref = map.remove(key);
return dereference(ref);
}
@Override
public void clear()
{
purgeQueue();
map.clear();
}
@Override
public Set<Map.Entry<K, V>> entrySet()
{
if (entrySet == null)
{
purgeQueue();
entrySet = new EntrySet();
}
return entrySet;
}
protected ReferenceQueue<V> createQueue()
{
return new ReferenceQueue<V>();
}
@SuppressWarnings("unchecked")
protected void purgeQueue()
{
if (queue != null)
{
KeyedReference<K, V> ref;
while ((ref = (KeyedReference<K, V>)queue.poll()) != null)
{
K key = ref.getKey();
map.remove(key);
purged(key);
}
}
}
protected void purged(K key)
{
}
protected V dereference(KeyedReference<K, V> ref)
{
if (ref == null)
{
return null;
}
return ref.get();
}
protected abstract KeyedReference<K, V> createReference(K key, V value, ReferenceQueue<V> queue);
/**
* @author Eike Stepper
*/
public static class Strong<K, V> extends ReferenceValueMap2<K, V>
{
public Strong()
{
}
public Strong(Map<K, KeyedReference<K, V>> map)
{
super(map);
}
@Override
protected KeyedReference<K, V> createReference(K key, V value, ReferenceQueue<V> queue)
{
return new KeyedStrongReference<K, V>(key, value);
}
@Override
protected ReferenceQueue<V> createQueue()
{
return null;
}
}
/**
* @author Eike Stepper
*/
public static class Soft<K, V> extends ReferenceValueMap2<K, V>
{
public Soft()
{
}
public Soft(Map<K, KeyedReference<K, V>> map)
{
super(map);
}
@Override
protected KeyedReference<K, V> createReference(K key, V value, ReferenceQueue<V> queue)
{
return new KeyedSoftReference<K, V>(key, value, queue);
}
}
/**
* @author Eike Stepper
*/
public static class Weak<K, V> extends ReferenceValueMap2<K, V>
{
public Weak()
{
}
public Weak(Map<K, KeyedReference<K, V>> map)
{
super(map);
}
@Override
protected KeyedReference<K, V> createReference(K key, V value, ReferenceQueue<V> queue)
{
return new KeyedWeakReference<K, V>(key, value, queue);
}
}
/**
* @author Eike Stepper
*/
private class EntrySet extends AbstractSet<Map.Entry<K, V>>
{
public EntrySet()
{
}
@Override
public int size()
{
return map.size();
}
@Override
public boolean isEmpty()
{
return map.isEmpty();
}
@Override
public boolean contains(Object object)
{
if (object == null)
{
throw new IllegalArgumentException("object == null"); //$NON-NLS-1$
}
if (object instanceof Map.Entry<?, ?>)
{
Map.Entry<?, ?> entry = (Map.Entry<?, ?>)object;
Object key = entry.getKey();
Object value = entry.getValue();
return key != null && value != null && value.equals(get(key));
}
return false;
}
@Override
public Iterator<Map.Entry<K, V>> iterator()
{
return new EntrySetIterator();
}
@Override
public Object[] toArray()
{
Object[] a = new Object[size()];
int i = 0;
for (Map.Entry<K, V> entry : this)
{
a[i++] = entry;
}
return a;
}
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] a)
{
if (a == null)
{
throw new IllegalArgumentException("array == null"); //$NON-NLS-1$
}
int size = size();
if (a.length < size)
{
a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
}
int i = 0;
for (Map.Entry<K, V> entry : this)
{
a[i++] = (T)entry;
}
if (a.length > size)
{
a[size] = null;
}
return a;
}
@Override
public boolean remove(Object object)
{
if (object == null)
{
throw new IllegalArgumentException("object == null"); //$NON-NLS-1$
}
if (object instanceof Map.Entry<?, ?>)
{
Map.Entry<?, ?> entry = (Map.Entry<?, ?>)object;
return map.remove(entry.getKey()) != null;
}
return false;
}
@Override
public void clear()
{
map.clear();
}
}
/**
* @author Eike Stepper
*/
private class EntrySetIterator implements Iterator<Map.Entry<K, V>>
{
private Iterator<Entry<K, KeyedReference<K, V>>> it = map.entrySet().iterator();
private MapEntry<K, V> nextEntry;
private K lastKey;
public EntrySetIterator()
{
}
public boolean hasNext()
{
if (nextEntry != null)
{
return true;
}
while (it.hasNext())
{
Entry<K, KeyedReference<K, V>> entry = it.next();
lastKey = entry.getKey();
V value = dereference(entry.getValue());
if (value != null)
{
nextEntry = new MapEntry<K, V>(lastKey, value);
return true;
}
}
return false;
}
public Entry<K, V> next()
{
if (nextEntry == null)
{
if (!hasNext())
{
throw new NoSuchElementException();
}
}
try
{
return nextEntry;
}
finally
{
nextEntry = null;
}
}
public void remove()
{
if (lastKey == null)
{
throw new IllegalStateException("lastKey == null"); //$NON-NLS-1$
}
map.remove(lastKey);
lastKey = null;
nextEntry = null;
}
}
}