/*
* $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.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.vmutil.asserts.AssertMessages;
import org.arakhne.afc.vmutil.locale.Locale;
/**
* A array-based <tt>List</tt> implementation with <em>weak keys</em>.
* An entry in a <tt>WeakArrayList</tt> will automatically be removed when
* it is no longer in ordinary use.
*
* <p>The behavior of the <tt>WeakArrayList</tt> class depends in part upon
* the actions of the garbage collector, so several familiar (though not
* required) <tt>List</tt> invariants do not hold for this class. Because
* the garbage collector may discard values at any time, a
* <tt>WeakArrayList</tt> may behave as though an unknown thread is silently
* removing entries. In particular, even if you synchronize on a
* <tt>WeakArrayList</tt> instance and invoke none of its mutator methods, it
* is possible for the <tt>size</tt> method to return smaller values over
* time, for the <tt>isEmpty</tt> method to return <tt>false</tt> and
* then <tt>true</tt>, for the <tt>contains</tt> method to return
* <tt>true</tt> and later <tt>false</tt> for a given value, for the
* <tt>get</tt> method to return a value for a given key but later return
* <tt>null</tt>, for the <tt>add</tt> method to return
* <tt>null</tt> and the <tt>remove</tt> method to return
* <tt>false</tt> for a value that previously appeared to be in the list.
*
* <p>If this map does not use a "deep expurge" of the released references,
* it could contains <code>null</code> values that corresponds to
* values that are released by the garbage collector. If a "deep expurge"
* is used, all the values released by the garbage collector will be
* removed from the map.
*
* <p>"Deep expurge" consumes much more time that "No deep expurge". This is the
* reason why this feature is not activated by default.
*
* <p>The "deep expurge" feature was added to fix the uncoherent behavior
* of the garbage collector which seems to not always enqueued the
* released values (sometimes the queue is empty even if a value was released).
*
* @param <T> is the type of the array's elements.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public class WeakArrayList<T> extends AbstractList<T> {
/** This value represents a null value given by the user.
*/
private static final Object NULL_VALUE = new Object();
private final transient ReferenceQueue<T> queue = new ReferenceQueue<>();
private Object[] data;
private int size;
private boolean enquedElement;
private List<ReferenceListener> listeners;
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @exception IllegalArgumentException if the specified initial capacity
* is negative
*/
public WeakArrayList(int initialCapacity) {
assert initialCapacity >= 0 : AssertMessages.positiveOrZeroParameter();
this.data = new Object[initialCapacity];
this.size = 0;
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
@SuppressWarnings("checkstyle:magicnumber")
public WeakArrayList() {
this(10);
}
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param collection the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public WeakArrayList(Collection<? extends T> collection) {
this.data = new Object[collection.size()];
this.size = this.data.length;
int i = 0;
for (final T t : collection) {
this.data[i] = createRef(t);
i++;
}
}
/** Replies the null value given by the user by the corresponding null object.
*/
@SuppressWarnings("unchecked")
private static <T> T maskNull(T value) {
return (value == null) ? (T) NULL_VALUE : value;
}
/** Replies the value given by the user.
*/
private static <T> T unmaskNull(T value) {
return (value == NULL_VALUE) ? null : value;
}
@Pure
@Override
@SuppressWarnings("unchecked")
public String toString() {
final StringBuilder buffer = new StringBuilder();
Reference<T> ref;
T obj;
for (int i = 0; i < this.size; ++i) {
ref = (Reference<T>) this.data[i];
if (this.data[i] == null) {
obj = null;
} else {
obj = ref.get();
}
buffer.append('{');
buffer.append(obj == null ? null : obj.toString());
buffer.append('}');
}
return buffer.toString();
}
/** Create and replies the reference for the specified object.
*
* @param obj is the object on which a weak reference must be attached.
* @return the weak reference.
*/
private Reference<T> createRef(T obj) {
return new WeakReference<>(maskNull(obj), this.queue);
}
/**
* Increases the capacity of this <tt>WeakArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
@SuppressWarnings("checkstyle:magicnumber")
public void ensureCapacity(int minCapacity) {
this.modCount++;
final int oldCapacity = this.data.length;
if (minCapacity > oldCapacity) {
final Object[] oldData = this.data;
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
// minCapacity is usually close to size, so this is a win:
this.data = Arrays.copyOf(oldData, newCapacity);
}
}
/**
* Trims the capacity of this <tt>WeakArrayList</tt> instance to be the
* list's current size. An application can use this operation to minimize
* the storage of an <tt>WeakArrayList</tt> instance.
*/
public void trimToSize() {
this.modCount++;
final int oldCapacity = this.data.length;
if (this.size < oldCapacity) {
this.data = Arrays.copyOf(this.data, this.size);
}
}
/** Clean the references that was released.
*
* <p>Notifies the listeners if a reference was released.
*
* @return the size
*/
@SuppressWarnings("unchecked")
public int expurge() {
// clear out ref queue.
while (this.queue.poll() != null) {
this.enquedElement = true;
}
int j;
if (this.enquedElement) {
// Clear the table
Reference<? extends T> ref;
j = 0;
for (int i = 0; i < this.size; ++i) {
ref = (Reference<T>) this.data[i];
if ((ref == null) || (ref.isEnqueued()) || (ref.get() == null)) {
if (ref != null) {
ref.clear();
}
this.data[i] = null;
} else {
if (i != j) {
this.data[j] = this.data[i];
this.data[i] = null;
}
j++;
}
}
this.enquedElement = false;
} else {
j = this.size;
}
// Allocation of array may have caused GC, which may have caused
// additional entries to go stale. Removing these entries from the
// reference queue will make them eligible for reclamation.
while (this.queue.poll() != null) {
this.enquedElement = true;
}
final int oldSize = this.size;
this.size = j;
if (j < oldSize) {
fireReferenceRelease(oldSize - j);
}
return this.size;
}
/** Verify if the specified index is inside the array.
*
* @param index is the index totest
* @param allowLast indicates if the last elements is assumed to be valid or not.
*/
protected void assertRange(int index, boolean allowLast) {
final int csize = expurge();
if (index < 0) {
throw new IndexOutOfBoundsException(Locale.getString("E1", index)); //$NON-NLS-1$
}
if (allowLast && (index > csize)) {
throw new IndexOutOfBoundsException(Locale.getString("E2", csize, index)); //$NON-NLS-1$
}
if (!allowLast && (index >= csize)) {
throw new IndexOutOfBoundsException(Locale.getString("E3", csize, index)); //$NON-NLS-1$
}
}
@Pure
@Override
public int size() {
return expurge();
}
@Pure
@SuppressWarnings("unchecked")
@Override
public T get(int index) {
T value;
do {
assertRange(index, false);
value = ((Reference<T>) this.data[index]).get();
}
while (value == null);
return unmaskNull(value);
}
@SuppressWarnings("unchecked")
@Override
public T set(int index, T element) {
T oldValue;
Reference<T> ref;
do {
assertRange(index, false);
ref = (Reference<T>) this.data[index];
oldValue = ref.get();
}
while (oldValue == null);
ref.clear();
this.data[index] = createRef(element);
this.modCount++;
return unmaskNull(oldValue);
}
@Override
public void add(int index, T element) {
assertRange(index, true);
ensureCapacity(this.size + 1);
System.arraycopy(this.data, index, this.data, index + 1, this.size - index);
this.data[index] = createRef(element);
this.size++;
this.modCount++;
}
@SuppressWarnings("unchecked")
@Override
public T remove(int index) {
T oldValue;
Reference<T> ref;
do {
assertRange(index, false);
ref = (Reference<T>) this.data[index];
oldValue = ref.get();
}
while (oldValue == null);
ref.clear();
System.arraycopy(this.data, index + 1, this.data, index, this.size - index - 1);
this.data[this.size - 1] = null;
this.size--;
this.modCount++;
return unmaskNull(oldValue);
}
/** Add listener on reference's release.
*
* @param listener the listener.
*/
public void addReferenceListener(ReferenceListener listener) {
if (this.listeners == null) {
this.listeners = new LinkedList<>();
}
final List<ReferenceListener> list = this.listeners;
synchronized (list) {
list.add(listener);
}
}
/** Remove listener on reference's release.
*
* @param listener the listener.
*/
public void removeReferenceListener(ReferenceListener listener) {
final List<ReferenceListener> list = this.listeners;
if (list != null) {
synchronized (list) {
list.remove(listener);
if (list.isEmpty()) {
this.listeners = null;
}
}
}
}
/**
* Fire the reference release event.
*
* @param released is the count of released objects.
*/
protected void fireReferenceRelease(int released) {
final List<ReferenceListener> list = this.listeners;
if (list != null && !list.isEmpty()) {
for (final ReferenceListener listener : list) {
listener.referenceReleased(released);
}
}
}
}