com.mchange.v2.util.DoubleWeakHashMap Maven / Gradle / Ivy
/*
* Distributed as part of mchange-commons-java 0.2.11
*
* Copyright (C) 2015 Machinery For Change, Inc.
*
* Author: Steve Waldman
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of EITHER:
*
* 1) The GNU Lesser General Public License (LGPL), version 2.1, as
* published by the Free Software Foundation
*
* OR
*
* 2) The Eclipse Public License (EPL), version 1.0
*
* You may choose which license to accept if you wish to redistribute
* or modify this work. You may offer derivatives of this work
* under the license you have chosen, or you may provide the same
* choice of license which you have been offered here.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* You should have received copies of both LGPL v2.1 and EPL v1.0
* along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
* If not, the text of these licenses are currently available at
*
* LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php
*
*/
package com.mchange.v2.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import com.mchange.v1.util.AbstractMapEntry;
import com.mchange.v1.util.WrapperIterator;
//TODO -- ensure that cleanCleared() gets called only once, even in methods implemented
// as loops. (cleanCleared() is idempotent, so the repeated calls are okay,
// but they're wasteful.
/**
* This class is not Thread safe.
* Use in single threaded contexts, or contexts where
* single threaded-access can be guaranteed, or
* wrap with Collections.synchronizedMap().
*
* This class does not accept null keys or values.
*/
public class DoubleWeakHashMap implements Map
{
HashMap inner;
ReferenceQueue keyQ = new ReferenceQueue();
ReferenceQueue valQ = new ReferenceQueue();
CheckKeyHolder holder = new CheckKeyHolder();
Set userKeySet = null;
Collection valuesCollection = null;
public DoubleWeakHashMap()
{ this.inner = new HashMap(); }
public DoubleWeakHashMap(int initialCapacity)
{ this.inner = new HashMap( initialCapacity ); }
public DoubleWeakHashMap(int initialCapacity, float loadFactor)
{ this.inner = new HashMap( initialCapacity, loadFactor ); }
public DoubleWeakHashMap(Map m)
{
this();
putAll(m);
}
public void cleanCleared()
{
WKey wk;
while ((wk = (WKey) keyQ.poll()) != null)
inner.remove(wk);
WVal wv;
while ((wv = (WVal) valQ.poll()) != null)
inner.remove(wv.getWKey());
}
public void clear()
{
cleanCleared();
inner.clear();
}
public boolean containsKey(Object key)
{
cleanCleared();
try
{ return inner.containsKey( holder.set(key) ); }
finally
{ holder.clear(); }
}
public boolean containsValue(Object val)
{
for (Iterator ii = inner.values().iterator(); ii.hasNext();)
{
WVal wval = (WVal) ii.next();
if (val.equals(wval.get()))
return true;
}
return false;
}
public Set entrySet()
{
cleanCleared();
return new UserEntrySet();
}
public Object get(Object key)
{
try
{
cleanCleared();
WVal wval = (WVal) inner.get(holder.set(key));
return (wval == null ? null : wval.get());
}
finally
{ holder.clear(); }
}
public boolean isEmpty()
{
cleanCleared();
return inner.isEmpty();
}
public Set keySet()
{
cleanCleared();
if (userKeySet == null)
userKeySet = new UserKeySet();
return userKeySet;
}
public Object put(Object key, Object val)
{
cleanCleared();
WVal wout = doPut(key, val);
if (wout != null)
return wout.get();
else
return null;
}
private WVal doPut(Object key, Object val)
{
WKey wk = new WKey(key, keyQ);
WVal wv = new WVal(wk, val, valQ);
return (WVal) inner.put(wk, wv);
}
public void putAll(Map m)
{
cleanCleared();
for (Iterator ii = m.entrySet().iterator(); ii.hasNext();)
{
Map.Entry entry = (Map.Entry) ii.next();
this.doPut( entry.getKey(), entry.getValue() );
}
}
public Object remove(Object key)
{
try
{
cleanCleared();
WVal wv = (WVal) inner.remove( holder.set(key) );
return (wv == null ? null : wv.get());
}
finally
{ holder.clear(); }
}
public int size()
{
cleanCleared();
return inner.size();
}
public Collection values()
{
if (valuesCollection == null)
this.valuesCollection = new ValuesCollection();
return valuesCollection;
}
final static class CheckKeyHolder
{
Object checkKey;
public Object get()
{return checkKey; }
public CheckKeyHolder set(Object ck)
{
assert this.checkKey == null : "Illegal concurrenct use of DoubleWeakHashMap!";
this.checkKey = ck;
return this;
}
public void clear()
{ checkKey = null; }
public int hashCode()
{ return checkKey.hashCode(); }
public boolean equals(Object o)
{
assert this.get() != null : "CheckedKeyHolder should never do an equality check while its value is null." ;
if (this == o)
return true;
else if (o instanceof CheckKeyHolder)
return this.get().equals( ((CheckKeyHolder) o).get() );
else if (o instanceof WKey)
return this.get().equals( ((WKey) o).get() );
else
return false;
}
}
final static class WKey extends WeakReference
{
int cachedHash;
WKey(Object keyObj, ReferenceQueue rq)
{
super(keyObj, rq);
this.cachedHash = keyObj.hashCode();
}
public int hashCode()
{ return cachedHash; }
public boolean equals(Object o)
{
if (this == o)
return true;
else if (o instanceof WKey)
{
WKey oo = (WKey) o;
Object myVal = this.get();
Object ooVal = oo.get();
if (myVal == null || ooVal == null)
return false;
else
return myVal.equals(ooVal);
}
else if (o instanceof CheckKeyHolder)
{
CheckKeyHolder oo = (CheckKeyHolder) o;
Object myVal = this.get();
Object ooVal = oo.get();
if (myVal == null || ooVal == null)
return false;
else
return myVal.equals(ooVal);
}
else
return false;
}
}
final static class WVal extends WeakReference
{
WKey key;
WVal(WKey key, Object valObj, ReferenceQueue rq)
{
super(valObj, rq);
this.key = key;
}
public WKey getWKey()
{ return key; }
}
private final class UserEntrySet extends AbstractSet
{
private Set innerEntrySet()
{
cleanCleared();
return inner.entrySet();
}
public Iterator iterator()
{
return new WrapperIterator(innerEntrySet().iterator(), true)
{
protected Object transformObject(Object o)
{
Entry innerEntry = (Entry) o;
Object key = ((WKey) innerEntry.getKey()).get();
Object val = ((WVal) innerEntry.getValue()).get();
if (key == null || val == null)
return WrapperIterator.SKIP_TOKEN;
else
return new UserEntry( innerEntry, key, val );
}
};
}
public int size()
{ return innerEntrySet().size(); }
}
class UserEntry extends AbstractMapEntry
{
Entry innerEntry;
Object key;
Object val;
UserEntry(Entry innerEntry, Object key, Object val)
{
this.innerEntry = innerEntry;
this.key = key;
this.val = val;
}
public final Object getKey()
{ return key; }
public final Object getValue()
{ return val; }
public final Object setValue(Object value)
{ return innerEntry.setValue( new WVal( (WKey) innerEntry.getKey() ,value, valQ) ); }
}
class UserKeySet implements Set
{
public boolean add(Object o)
{
cleanCleared();
throw new UnsupportedOperationException("You cannot add to a Map's key set.");
}
public boolean addAll(Collection c)
{
cleanCleared();
throw new UnsupportedOperationException("You cannot add to a Map's key set.");
}
public void clear()
{ DoubleWeakHashMap.this.clear(); }
public boolean contains(Object o)
{
return DoubleWeakHashMap.this.containsKey(o);
}
public boolean containsAll(Collection c)
{
for (Iterator ii = c.iterator(); ii.hasNext();)
if (! this.contains(ii.next()))
return false;
return true;
}
public boolean isEmpty()
{ return DoubleWeakHashMap.this.isEmpty(); }
public Iterator iterator()
{
cleanCleared();
return new WrapperIterator(DoubleWeakHashMap.this.inner.keySet().iterator(), true)
{
protected Object transformObject(Object o)
{
Object key = ((WKey) o).get();
if (key == null)
return WrapperIterator.SKIP_TOKEN;
else
return key;
}
};
}
public boolean remove(Object o)
{
return (DoubleWeakHashMap.this.remove(o) != null);
}
public boolean removeAll(Collection c)
{
boolean out = false;
for (Iterator ii = c.iterator(); ii.hasNext();)
out |= this.remove(ii.next());
return out;
}
public boolean retainAll(Collection c)
{
//we implicitly cleanCleared() by calling iterator()
boolean out = false;
for (Iterator ii = this.iterator(); ii.hasNext();)
{
if (!c.contains(ii.next()))
{
ii.remove();
out = true;
}
}
return out;
}
public int size()
{ return DoubleWeakHashMap.this.size(); }
public Object[] toArray()
{
cleanCleared();
return new HashSet( this ).toArray();
}
public Object[] toArray(Object[] array)
{
cleanCleared();
return new HashSet( this ).toArray(array);
}
}
class ValuesCollection implements Collection
{
public boolean add(Object o)
{
cleanCleared();
throw new UnsupportedOperationException("DoubleWeakHashMap does not support adding to its values Collection.");
}
public boolean addAll(Collection c)
{
cleanCleared();
throw new UnsupportedOperationException("DoubleWeakHashMap does not support adding to its values Collection.");
}
public void clear()
{ DoubleWeakHashMap.this.clear(); }
public boolean contains(Object o)
{ return DoubleWeakHashMap.this.containsValue(o); }
public boolean containsAll(Collection c)
{
for (Iterator ii = c.iterator(); ii.hasNext();)
if (!this.contains(ii.next()))
return false;
return true;
}
public boolean isEmpty()
{ return DoubleWeakHashMap.this.isEmpty(); }
public Iterator iterator()
{
return new WrapperIterator(inner.values().iterator(), true)
{
protected Object transformObject(Object o)
{
Object val = ((WVal) o).get();
if (val == null)
return WrapperIterator.SKIP_TOKEN;
else
return val;
}
};
}
public boolean remove(Object o)
{
cleanCleared();
return removeValue(o);
}
public boolean removeAll(Collection c)
{
cleanCleared();
boolean out = false;
for (Iterator ii = c.iterator(); ii.hasNext();)
out |= removeValue(ii.next());
return out;
}
public boolean retainAll(Collection c)
{
cleanCleared();
return retainValues(c);
}
public int size()
{ return DoubleWeakHashMap.this.size(); }
public Object[] toArray()
{
cleanCleared();
return new ArrayList(this).toArray();
}
public Object[] toArray(Object[] array)
{
cleanCleared();
return new ArrayList(this).toArray(array);
}
private boolean removeValue(Object val)
{
boolean out = false;
for (Iterator ii = inner.values().iterator(); ii.hasNext();)
{
WVal wv = (WVal) ii.next();
if (val.equals(wv.get()))
{
ii.remove();
out = true;
}
}
return out;
}
private boolean retainValues(Collection c)
{
boolean out = false;
for (Iterator ii = inner.values().iterator(); ii.hasNext();)
{
WVal wv = (WVal) ii.next();
if (! c.contains(wv.get()) )
{
ii.remove();
out = true;
}
}
return out;
}
}
/*
public static void main(String[] argv)
{
DoubleWeakHashMap m = new DoubleWeakHashMap();
//Set keySet = new HashSet();
//Set valSet = new HashSet();
while (true)
{
System.err.println( m.inner.size() );
//if (Math.random() < 0.1f)
// valSet.clear();
Object key = new Object();
Object val = new long[100000];
//keySet.add(key);
//valSet.add(val);
m.put( key, val );
}
}
*/
}