/*
* $Id$
* This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc
*
* Copyright (c) 2000-2012 Stephane GALLAND.
* Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports,
* Universite de Technologie de Belfort-Montbeliard.
* Copyright (c) 2013-2016 The original authors, and other authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.arakhne.afc.references;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.vmutil.locale.Locale;
/**
* A <tt>Map</tt> implementation with <em>weak/soft values</em>. An entry in a
* <tt>AbstractReferencedValueMap</tt> will automatically be removed when its value is no
* longer in ordinary use or <code>null</code>.
*
* <p>This abstract implementation does not decide if the map is based on a tree or on a hashtable;
* it does not impose soft or weak references.
*
* @param <K> is the type of the keys.
* @param <V> is the type of the values.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 5.8
*/
public abstract class AbstractReferencedValueMap<K, V> extends AbstractMap<K, V> {
/** Defines the NULL object inside a WeakValueMap.
*
* @see #maskNull(Object)
*/
protected static final Object NULL_VALUE = new Object();
/** Internal map.
*/
protected final Map<K, ReferencableValue<K, V>> map;
private boolean autoExpurge;
private final ReferenceQueue<V> queue = new ReferenceQueue<>();
/**
* Constructs an empty <tt>Map</tt>.
*
* @param map is the map instance to use to store the entries.
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public AbstractReferencedValueMap(Map<K, ReferencableValue<K, V>> map) {
this.map = map;
}
/** Mask the null values given by the used of this map.
*
* <p>This method replaces the <code>null</code> value by
* the internal representation {@link #NULL_VALUE}.
*
* @param <VALUET> is the type of the value.
* @param value is the value given by the user of this map.
* @return the internal representation of the value.
* @see #unmaskNull(Object)
*/
@Pure
@SuppressWarnings("unchecked")
protected static <VALUET> VALUET maskNull(VALUET value) {
return (value == null) ? (VALUET) NULL_VALUE : value;
}
/** Unmask the null values given by the used of this map.
*
* <p>This method replaces the internal representation
* {@link #NULL_VALUE} of null values by its user representation
* <code>null</code>.
*
* @param <VALUET> is the type of the value.
* @param value is the value given by the user of this map.
* @return the internal representation of the value.
* @see #maskNull(Object)
*/
@Pure
protected static <VALUET> VALUET unmaskNull(VALUET value) {
return (value == NULL_VALUE) ? null : value;
}
/**
* Reallocates the array being used within toArray when the iterator
* returned more elements than expected, and finishes filling it from
* the iterator.
*
* @param <T> the type of the elements in the array.
* @param array the array, replete with previously stored elements
* @param it the in-progress iterator over this collection
* @return array containing the elements in the given array, plus any
* further elements returned by the iterator, trimmed to size
*/
@SuppressWarnings("unchecked")
static <T> T[] finishToArray(T[] array, Iterator<?> it) {
T[] rp = array;
int i = rp.length;
while (it.hasNext()) {
final int cap = rp.length;
if (i == cap) {
int newCap = ((cap / 2) + 1) * 3;
if (newCap <= cap) {
// integer overflow
if (cap == Integer.MAX_VALUE) {
throw new OutOfMemoryError();
}
newCap = Integer.MAX_VALUE;
}
rp = Arrays.copyOf(rp, newCap);
}
rp[++i] = (T) it.next();
}
// trim if overallocated
return (i == rp.length) ? rp : Arrays.copyOf(rp, i);
}
/** Clean the references that was marked as released inside
* the queue.
*/
protected final void expurgeNow() {
if (this.autoExpurge) {
expurge();
} else {
expurgeQueuedReferences();
}
}
/** Replies if this map expurge all the released references
* even if they are not enqueued by the virtual machine.
*
* @return <code>true</code> is the values are deeply expurged when they
* are released from the moemory, otherwise <code>false</code>.
*/
@Pure
public final boolean isDeeplyExpurge() {
return this.autoExpurge;
}
/** Set if this map expurge all the released references
* even if they are not enqueued by the virtual machine.
*
* @param deeplyExpurge must be <code>true</code> to
* expurge all the released values, otherwise <code>false</code>
* to expurge only the enqueued values.
* @return the old value of this flag
*/
public final boolean setDeeplyExpurge(boolean deeplyExpurge) {
final boolean old = this.autoExpurge;
this.autoExpurge = deeplyExpurge;
return old;
}
/** Clean the references that was marked as released inside
* the queue.
*/
public final void expurgeQueuedReferences() {
Reference<? extends V> reference;
while ((reference = this.queue.poll()) != null) {
if (reference instanceof ReferencableValue<?, ?>) {
this.map.remove(((ReferencableValue<?, ?>) reference).getKey());
}
reference.clear();
}
}
/** Clean the references that was released.
*/
public final void expurge() {
final Iterator<Entry<K, ReferencableValue<K, V>>> iter = this.map.entrySet().iterator();
Entry<K, ReferencableValue<K, V>> entry;
ReferencableValue<K, V> value;
while (iter.hasNext()) {
entry = iter.next();
if (entry != null) {
value = entry.getValue();
if ((value != null)
&& ((value.isEnqueued()) || (value.get() == null))) {
value.enqueue();
value.clear();
}
}
}
Reference<? extends V> reference;
while ((reference = this.queue.poll()) != null) {
if (reference instanceof ReferencableValue<?, ?>) {
this.map.remove(((ReferencableValue<?, ?>) reference).getKey());
}
reference.clear();
}
}
/** Create a storage object that permits to put the specified
* elements inside this map.
*
* @param key is the key associated to the value
* @param value is the value
* @param refQueue is the reference queue to use
* @return the new storage object
*/
protected abstract ReferencableValue<K, V> makeValue(K key, V value, ReferenceQueue<V> refQueue);
/** Create a storage object that permits to put the specified
* elements inside this map.
*
* @param key is the key associated to the value
* @param value is the value
* @return the new storage object
*/
protected final ReferencableValue<K, V> makeValue(K key, V value) {
return makeValue(key, value, this.queue);
}
@Override
public final V put(K key, V value) {
expurgeNow();
final ReferencableValue<K, V> ret = this.map.put(key, makeValue(key, value, this.queue));
if (ret == null) {
return null;
}
return ret.getValue();
}
@Pure
@Override
public final Set<Entry<K, V>> entrySet() {
expurgeNow();
return new InnerEntrySet();
}
@Pure
@Override
public final boolean equals(Object obj) {
expurgeNow();
return super.equals(obj);
}
@Pure
@Override
public final int hashCode() {
expurgeNow();
return super.hashCode();
}
/**
* This interface provides information about the pairs inside
* a map with weak/soft reference values.
*
* @param <K> is the type of the map keys.
* @param <V> is the type of the map values.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
protected interface ReferencableValue<K, V> extends Entry<K, V> {
/**
* @return if the value is enqueued into a reference queue.
*/
boolean isEnqueued();
/**
* @return the weak/soft reference.
*/
V get();
/** Enqueue the value.
*
* @return if the value was enqueued
*/
boolean enqueue();
/** Clear the reference.
*/
void clear();
}
/** Internal implementation of a set.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class InnerEntrySet implements Set<Entry<K, V>> {
/** Constructor.
*/
InnerEntrySet() {
//
}
@Override
public final boolean add(java.util.Map.Entry<K, V> element) {
final K key = element.getKey();
final V value = element.getValue();
return AbstractReferencedValueMap.this.map.put(key, makeValue(key, value)) == null;
}
@Override
public final boolean addAll(Collection<? extends java.util.Map.Entry<K, V>> collection) {
boolean changed = true;
for (final java.util.Map.Entry<K, V> entry : collection) {
changed = add(entry) || changed;
}
return changed;
}
@Override
public final void clear() {
AbstractReferencedValueMap.this.map.clear();
}
@Override
public final boolean contains(Object element) {
if (element instanceof Entry<?, ?>) {
try {
expurgeNow();
return AbstractReferencedValueMap.this.map.containsKey(((Entry<?, ?>) element).getKey());
} catch (AssertionError e) {
throw e;
} catch (Throwable exception) {
//
}
}
return false;
}
@Override
public final boolean containsAll(Collection<?> collection) {
boolean ok;
expurgeNow();
for (final Object o : collection) {
ok = false;
if (o instanceof Entry<?, ?>) {
try {
ok = AbstractReferencedValueMap.this.map.containsKey(((Entry<?, ?>) o).getKey());
} catch (AssertionError e) {
throw e;
} catch (Throwable exception) {
//
}
}
if (!ok) {
return false;
}
}
return true;
}
@Override
public final boolean isEmpty() {
expurgeNow();
return AbstractReferencedValueMap.this.map.isEmpty();
}
@Override
public final Iterator<java.util.Map.Entry<K, V>> iterator() {
return new InnerIterator();
}
@Override
public final boolean remove(Object element) {
if (element instanceof Entry<?, ?>) {
try {
return AbstractReferencedValueMap.this.map.remove(((Entry<?, ?>) element).getKey()) != null;
} catch (AssertionError e) {
throw e;
} catch (Throwable exception) {
//
}
}
return false;
}
@Override
public final boolean removeAll(Collection<?> collection) {
boolean changed = true;
for (final Object o : collection) {
changed = remove(o) || changed;
}
return changed;
}
@Override
public final boolean retainAll(Collection<?> collection) {
expurgeNow();
final Collection<K> keys = AbstractReferencedValueMap.this.map.keySet();
final Iterator<K> iterator = keys.iterator();
K key;
boolean changed = false;
while (iterator.hasNext()) {
key = iterator.next();
if (!collection.contains(key)) {
iterator.remove();
changed = true;
}
}
return changed;
}
@Override
public final int size() {
expurgeNow();
return AbstractReferencedValueMap.this.map.size();
}
@Override
public final Object[] toArray() {
expurgeNow();
final Object[] tab = new Object[AbstractReferencedValueMap.this.map.size()];
return toArray(tab);
}
@SuppressWarnings("unchecked")
@Override
public final <T> T[] toArray(T[] array) {
expurgeNow();
// Estimate size of array; be prepared to see more or fewer elements
final int size = AbstractReferencedValueMap.this.map.size();
final T[] r = array.length >= size ? array
: (T[]) Array.newInstance(array.getClass().getComponentType(), size);
final Iterator<Entry<K, V>> it = iterator();
for (int i = 0; i < r.length; ++i) {
if (!it.hasNext()) {
// fewer elements than expected
if (array != r) {
return Arrays.copyOf(r, i);
}
// null-terminate
r[i] = null;
return r;
}
r[i] = (T) it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
}
/** Internal implementation of an iterator.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class InnerIterator implements Iterator<Entry<K, V>> {
private final Iterator<Entry<K, ReferencableValue<K, V>>> originalIterator;
private Entry<K, V> next;
private boolean nextSearchProceeded;
private boolean enableRemove;
InnerIterator() {
this.originalIterator = AbstractReferencedValueMap.this.map.entrySet().iterator();
}
private void searchNext() {
if (!this.nextSearchProceeded) {
this.nextSearchProceeded = true;
this.next = null;
Entry<K, ReferencableValue<K, V>> originalNext;
ReferencableValue<K, V> wvalue;
while (this.next == null && this.originalIterator.hasNext()) {
originalNext = this.originalIterator.next();
if (originalNext != null) {
wvalue = originalNext.getValue();
if (wvalue != null) {
this.next = new InnerEntry(
originalNext.getKey(),
wvalue.getValue(),
originalNext);
return;
}
}
// Remove the original entry because the pointer was lost.
this.originalIterator.remove();
}
}
}
@Override
public boolean hasNext() {
searchNext();
assert this.nextSearchProceeded;
this.enableRemove = false;
return this.next != null;
}
@Override
public java.util.Map.Entry<K, V> next() {
searchNext();
assert this.nextSearchProceeded;
final Entry<K, V> cnext = this.next;
// Reset the research flags
this.next = null;
this.nextSearchProceeded = false;
this.enableRemove = true;
return cnext;
}
@Override
public void remove() {
if (!this.enableRemove) {
throw new IllegalStateException(Locale.getString("E1")); //$NON-NLS-1$
}
this.originalIterator.remove();
}
}
/** Internal implementation of a map entry.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class InnerEntry implements Entry<K, V> {
private final Entry<K, ReferencableValue<K, V>> original;
private final K key;
private V value;
InnerEntry(K key, V value, Entry<K, ReferencableValue<K, V>> original) {
this.original = original;
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return this.key;
}
@Override
public V getValue() {
return this.value;
}
@Override
public V setValue(V value) {
this.value = value;
return this.original.getValue().setValue(value);
}
}
/**
* Value stored in a {@link AbstractReferencedValueMap} inside a {@link SoftReference}.
*
* @param <VKT> is the type of the key associated to the value.
* @param <VVT> is the type of the value.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
protected static class SoftReferencedValue<VKT, VVT> extends SoftReference<VVT> implements ReferencableValue<VKT, VVT> {
private final VKT key;
/**
* @param key is the key.
* @param value is the value.
* @param queue is the memory-release listener.
*/
public SoftReferencedValue(VKT key, VVT value, ReferenceQueue<VVT> queue) {
super(maskNull(value), queue);
this.key = key;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append('{');
final VKT key = getKey();
buffer.append(key == null ? null : key.toString());
buffer.append('=');
if (isEnqueued()) {
buffer.append("Q#"); //$NON-NLS-1$
} else {
buffer.append("P#"); //$NON-NLS-1$
}
final VVT v = getValue();
buffer.append(v == null ? null : v.toString());
buffer.append('}');
return buffer.toString();
}
@Override
public VKT getKey() {
return this.key;
}
@Override
public VVT getValue() {
return unmaskNull(get());
}
@Override
public VVT setValue(VVT value) {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj instanceof Entry) {
final Entry<VKT, VVT> e = (Entry<VKT, VVT>) obj;
final Object e1val = getValue();
final Object e2val = e.getValue();
return (getKey() == null
? e.getKey() == null
: getKey().equals(e.getKey()))
&& (e1val == null ? e2val == null : e1val.equals(e2val));
}
return false;
}
} // class SoftReferencedValue
/**
* Value stored in a {@link AbstractReferencedValueMap} inside a {@link WeakReference}.
*
* @param <VKT> is the type of the key associated to the value.
* @param <VVT> is the type of the value.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
protected static class WeakReferencedValue<VKT, VVT> extends WeakReference<VVT> implements ReferencableValue<VKT, VVT> {
private final VKT key;
/**
* @param key is the key.
* @param value is the value.
* @param queue is the memory-release listener.
*/
public WeakReferencedValue(VKT key, VVT value, ReferenceQueue<VVT> queue) {
super(maskNull(value), queue);
this.key = key;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append('{');
final VKT key = getKey();
buffer.append(key == null ? null : key.toString());
buffer.append('=');
if (isEnqueued()) {
buffer.append("Q#"); //$NON-NLS-1$
} else {
buffer.append("P#"); //$NON-NLS-1$
}
final VVT v = getValue();
buffer.append(v == null ? null : v.toString());
buffer.append('}');
return buffer.toString();
}
@Override
public VKT getKey() {
return this.key;
}
@Override
public VVT getValue() {
return unmaskNull(get());
}
@Override
public VVT setValue(VVT value) {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj instanceof Entry) {
final Entry<VKT, VVT> e = (Entry<VKT, VVT>) obj;
final Object e1val = getValue();
final Object e2val = e.getValue();
return (getKey() == null
? e.getKey() == null : getKey().equals(e.getKey()))
&& (e1val == null ? e2val == null : e1val.equals(e2val));
}
return false;
}
}
/**
* Value stored in a {@link AbstractReferencedValueMap} inside a {@link PhantomReference}.
*
* @param <VKT> is the type of the key associated to the value.
* @param <VVT> is the type of the value.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
protected static class PhantomReferencedValue<VKT, VVT> extends PhantomReference<VVT> implements ReferencableValue<VKT, VVT> {
private final VKT key;
/**
* @param key is the key.
* @param value is the value.
* @param queue is the memory-release listener.
*/
public PhantomReferencedValue(VKT key, VVT value, ReferenceQueue<VVT> queue) {
super(maskNull(value), queue);
this.key = key;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append('{');
final VKT key = getKey();
buffer.append(key == null ? null : key.toString());
buffer.append('=');
if (isEnqueued()) {
buffer.append("Q#"); //$NON-NLS-1$
} else {
buffer.append("P#"); //$NON-NLS-1$
}
final VVT v = getValue();
buffer.append(v == null ? null : v.toString());
buffer.append('}');
return buffer.toString();
}
@Override
public VKT getKey() {
return this.key;
}
@Override
public VVT getValue() {
return unmaskNull(get());
}
@Override
public VVT setValue(VVT value) {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj instanceof Entry) {
final Entry<VKT, VVT> e = (Entry<VKT, VVT>) obj;
final Object e1val = getValue();
final Object e2val = e.getValue();
return (getKey() == null
? e.getKey() == null : getKey().equals(e.getKey()))
&& (e1val == null ? e2val == null : e1val.equals(e2val));
}
return false;
}
}
}