/*
* $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.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractSet;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.vmutil.locale.Locale;
/**
* A <tt>Set</tt> implementation with {@link SoftReference soft values}
* or {@link WeakReference weak values}. An entry in a
* <tt>AbstractReferencedSet</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.
*
* @param <E> is the type of the values.
* @param <R> is the type of the references.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 5.9
*/
public abstract class AbstractReferencedSet<E, R extends Reference<E>> extends AbstractSet<E> {
private boolean autoExpurge;
private final ReferenceQueue<E> queue = new ReferenceQueue<>();
private final Class<? super R> referenceType;
private final Set<R> theSet;
/**
* @param theSet is the internal data structure to use.
* @param referenceType is the type of the references.
*/
public AbstractReferencedSet(Set<R> theSet, Class<? super R> referenceType) {
assert theSet != null;
assert referenceType != null;
this.theSet = theSet;
this.referenceType = referenceType;
}
/** Create a reference on the given object.
*
* @param element is the element to wrap into a reference
* @return the reference of the given element.
*/
protected abstract R createReference(E element);
/** 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 E> obj;
while ((obj = this.queue.poll()) != null) {
obj.clear();
if (this.referenceType.isInstance(obj)) {
this.theSet.remove(this.referenceType.cast(obj));
}
}
}
/** Clean the references that was released.
*/
public final void expurge() {
Reference<? extends E> obj;
final Iterator<R> iter = this.theSet.iterator();
R reference;
while (iter.hasNext()) {
reference = iter.next();
if (reference != null
&& ((reference.isEnqueued()) || (reference.get() == null))) {
reference.enqueue();
reference.clear();
}
}
while ((obj = this.queue.poll()) != null) {
obj.clear();
if (this.referenceType.isInstance(obj)) {
this.theSet.remove(this.referenceType.cast(obj));
}
}
}
@Pure
@Override
public final boolean equals(Object obj) {
expurgeNow();
return super.equals(obj);
}
@Pure
@Override
public final int hashCode() {
expurgeNow();
return super.hashCode();
}
@Pure
@Override
public Iterator<E> iterator() {
expurgeNow();
return new InnerIterator();
}
@Pure
@Override
public int size() {
expurgeNow();
return this.theSet.size();
}
@Override
public boolean add(E value) {
return this.theSet.add(createReference(value));
}
/** Iterator.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 5.9
*/
private class InnerIterator implements Iterator<E> {
private final Iterator<R> originalIterator;
private E next;
private boolean nextSearchProceeded;
private boolean enableRemove;
/** Construct the iterator.
*/
@SuppressWarnings("synthetic-access")
InnerIterator() {
this.originalIterator = AbstractReferencedSet.this.theSet.iterator();
}
private void searchNext() {
if (!this.nextSearchProceeded) {
this.nextSearchProceeded = true;
this.next = null;
R originalNext;
E wvalue;
while (this.next == null && this.originalIterator.hasNext()) {
originalNext = this.originalIterator.next();
if (originalNext != null) {
wvalue = originalNext.get();
if (wvalue != null) {
this.next = wvalue;
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 E next() {
searchNext();
assert this.nextSearchProceeded;
final E 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();
}
}
/** Comparator wrapper.
*
* @param <E> is the type of the values.
* @param <R> is the type of the references.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 5.9
*/
protected static class ReferenceComparator<E, R extends Reference<E>> implements Comparator<R> {
private final Comparator<? super E> comparator;
/** Construct a comparator reference.
*
* @param comparator the comparator.
*/
public ReferenceComparator(Comparator<? super E> comparator) {
assert comparator != null;
this.comparator = comparator;
}
@Override
public int compare(R o1, R o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return Integer.MIN_VALUE;
}
if (o2 == null) {
return Integer.MAX_VALUE;
}
return this.comparator.compare(o1.get(), o2.get());
}
}
}