[COLLECTIONS-696] AbstractReferenceMap made easier for subclassing; PR
#51.
This commit is contained in:
parent
ad442e3c73
commit
2374711606
|
@ -400,13 +400,15 @@ public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V>
|
|||
HashEntry<K, V> previous = null;
|
||||
HashEntry<K, V> entry = data[index];
|
||||
while (entry != null) {
|
||||
if (((ReferenceEntry<K, V>) entry).purge(ref)) {
|
||||
ReferenceEntry<K, V> refEntry = (ReferenceEntry<K, V>) entry;
|
||||
if (refEntry.purge(ref)) {
|
||||
if (previous == null) {
|
||||
data[index] = entry.next;
|
||||
} else {
|
||||
previous.next = entry.next;
|
||||
}
|
||||
this.size--;
|
||||
refEntry.onPurge();
|
||||
return;
|
||||
}
|
||||
previous = entry;
|
||||
|
@ -721,12 +723,18 @@ public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V>
|
|||
throw new Error();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the callback for custom "after purge" logic
|
||||
*/
|
||||
protected void onPurge() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Purges the specified reference
|
||||
* @param ref the reference to purge
|
||||
* @return true or false
|
||||
*/
|
||||
boolean purge(final Reference<?> ref) {
|
||||
protected boolean purge(final Reference<?> ref) {
|
||||
boolean r = parent.keyType != ReferenceStrength.HARD && key == ref;
|
||||
r = r || parent.valueType != ReferenceStrength.HARD && value == ref;
|
||||
if (r) {
|
||||
|
@ -736,7 +744,7 @@ public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V>
|
|||
if (parent.valueType != ReferenceStrength.HARD) {
|
||||
((Reference<?>) value).clear();
|
||||
} else if (parent.purgeValues) {
|
||||
value = null;
|
||||
nullValue();
|
||||
}
|
||||
}
|
||||
return r;
|
||||
|
@ -750,6 +758,13 @@ public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V>
|
|||
protected ReferenceEntry<K, V> next() {
|
||||
return (ReferenceEntry<K, V>) next;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be overriden to provide custom logic to purge value
|
||||
*/
|
||||
protected void nullValue() {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
@ -1073,4 +1088,13 @@ public abstract class AbstractReferenceMap<K, V> extends AbstractHashedMap<K, V>
|
|||
protected boolean isKeyType(final ReferenceStrength type) {
|
||||
return this.keyType == type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided protected read-only access to the value type.
|
||||
* @param type the type to check against.
|
||||
* @return true if valueType has the specified type
|
||||
*/
|
||||
protected boolean isValueType(final ReferenceStrength type) {
|
||||
return this.valueType == type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,20 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.Test;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.collections4.BulkTest;
|
||||
import org.apache.commons.collections4.map.AbstractHashedMap.HashEntry;
|
||||
import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceEntry;
|
||||
import org.apache.commons.collections4.map.AbstractReferenceMap.ReferenceStrength;
|
||||
|
||||
import junit.framework.Test;
|
||||
|
||||
/**
|
||||
* Tests for ReferenceMap.
|
||||
*
|
||||
|
@ -255,6 +260,40 @@ public class ReferenceMapTest<K, V> extends AbstractIterableMapTest<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
public void testCustomPurge() {
|
||||
List<Integer> expiredValues = new ArrayList<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
final Consumer<Integer> consumer = (Consumer<Integer> & Serializable) v -> expiredValues.add(v);
|
||||
final Map<Integer, Integer> map = new ReferenceMap<Integer, Integer>(ReferenceStrength.WEAK, ReferenceStrength.HARD, false) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected ReferenceEntry<Integer, Integer> createEntry(HashEntry<Integer, Integer> next, int hashCode, Integer key, Integer value) {
|
||||
return new AccessibleEntry<>(this, next, hashCode, key, value, consumer);
|
||||
}
|
||||
};
|
||||
for (int i = 100000; i < 100010; i++) {
|
||||
map.put(Integer.valueOf(i), Integer.valueOf(i));
|
||||
}
|
||||
int iterations = 0;
|
||||
int bytz = 2;
|
||||
while (true) {
|
||||
System.gc();
|
||||
if (iterations++ > 50 || bytz < 0) {
|
||||
fail("Max iterations reached before resource released.");
|
||||
}
|
||||
map.isEmpty();
|
||||
if (!expiredValues.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
// create garbage:
|
||||
@SuppressWarnings("unused")
|
||||
final byte[] b = new byte[bytz];
|
||||
bytz = bytz * 2;
|
||||
}
|
||||
assertFalse("Value should be stored", expiredValues.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether after serialization the "data" HashEntry array is the same size as the original.<p>
|
||||
*
|
||||
|
@ -292,4 +331,21 @@ public class ReferenceMapTest<K, V> extends AbstractIterableMapTest<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
private static class AccessibleEntry<K, V> extends ReferenceEntry<K, V> {
|
||||
final AbstractReferenceMap<K, V> parent;
|
||||
final Consumer<V> consumer;
|
||||
|
||||
public AccessibleEntry(final AbstractReferenceMap<K, V> parent, final HashEntry<K, V> next, final int hashCode, final K key, final V value, final Consumer<V> consumer) {
|
||||
super(parent, next, hashCode, key, value);
|
||||
this.parent = parent;
|
||||
this.consumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPurge() {
|
||||
if (parent.isValueType(ReferenceStrength.HARD)) {
|
||||
consumer.accept(getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue