/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
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.ConcurrentModificationException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/** Set which holds its members by using of WeakReferences.
* MT level: unsafe.
* Note: as of JDK 6.0 (b51), you can instead use
*
* Set<T> s = Collections.newSetFromMap(new WeakHashMap<T, Boolean>());
*
*
* @author Ales Novak
*/
public class WeakSet extends AbstractSet implements Cloneable, Serializable {
static final long serialVersionUID = 3062376055928236721L;
/** load factor */
private float loadFactor;
/** Number of items. */
private int size;
/** Modification count */
private long modcount;
/** Reference queue of collected weak refs */
private transient ReferenceQueue refq;
/** Count of null in this set */
long nullCount;
/** An array of Entries */
private transient Entry[] entries;
transient Entry iterChain;
/** Constructs a new set. */
public WeakSet() {
this(11, 0.75f);
}
/** Constructs a new set containing the elements in the specified collection.
* @param c a collection to add
*/
public WeakSet(Collection extends E> c) {
this();
addAll(c);
}
/** Constructs a new, empty set;
* @param initialCapacity initial capacity
*/
public WeakSet(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/** Constructs a new, empty set;
*
* @param initialCapacity initial capacity
* @param loadFactor load factor
*/
public WeakSet(int initialCapacity, float loadFactor) {
if ((initialCapacity <= 0) || (loadFactor <= 0)) {
throw new IllegalArgumentException();
}
size = 0;
modcount = 0;
this.loadFactor = loadFactor;
nullCount = 0;
refq = new ReferenceQueue();
entries = Entry.createArray(initialCapacity);
iterChain = null;
}
/**
* logs iterator chain (for debugging)
* @param msg
*/
void logIterChain(String msg) {
Logger log = Logger.getLogger(WeakSet.class.getName());
log.log(Level.FINE, msg);
if (iterChain == null) {
log.log(Level.FINE, "Empty");
return;
}
StringBuilder str = new StringBuilder();
Entry it = iterChain;
str.append(size + ": ");
while (it != null) {
str.append(it.get() + "(" + it.hashcode + ")" + "->");
it = it.iterChainNext;
}
log.log(Level.FINE, str.toString());
}
/** Adds the specified element to this set if it is not already present.
*
* @param o an Object to add
*/
public boolean add(E o) {
if (o == null) {
size++;
nullCount++;
modcount++;
return true;
}
Entry e = object2Entry(o);
if (e != null) {
return false;
}
modcount++;
size++;
int hash = hashIt(o);
Entry next = entries[hash];
iterChain = entries[hash] = new Entry(this, o, refq, next, iterChain);
rehash();
return true;
}
/** Removes all of the elements from this set. */
public void clear() {
for (int i = 0; i < entries.length; i++) {
entries[i] = null;
}
nullCount = 0;
modcount++;
size = 0;
iterChain = null;
}
/** Returns a shallow copy of this WeakSet instance: the elements themselves are not cloned. */
public Object clone() {
WeakSet nws = new WeakSet(1, loadFactor);
nws.size = size;
nws.nullCount = nullCount;
Entry[] cloned = Entry.createArray(entries.length);
nws.entries = cloned;
for (int i = 0; i < cloned.length; i++) {
Object ref;
if ((entries[i] == null) || ((ref = entries[i].get()) == null)) {
cloned[i] = null;
} else {
cloned[i] = ((entries[i] == null) ? null : entries[i].clone(nws.refq));
ref = null;
}
// chains into nws iterator chain
Entry entry = cloned[i];
while (entry != null) {
entry.chainIntoIter(nws.iterChain);
nws.iterChain = entry;
entry = entry.next;
}
}
return nws;
}
/** Returns true if this set contains the specified element.
*
* @param o an Object to examine
*/
public boolean contains(Object o) {
if (o == null) {
return nullCount > 0;
}
return object2Entry(o) != null;
}
/** Returns true if this set contains no elements.
*/
public boolean isEmpty() {
return ((nullCount == 0) && (size() == 0));
}
/** Returns an iterator over the elements in this set. */
public Iterator iterator() {
return new WeakSetIterator();
}
/** Removes the given element from this set if it is present.
*
* @param o an Object to remove
* @return true if and only if the Object was successfuly removed.
*/
public boolean remove(Object o) {
if (o == null) {
if (nullCount > 0) {
nullCount--;
modcount++;
size--;
}
return true;
}
Entry e = object2Entry(o);
if (e != null) {
modcount++;
size--;
e.remove();
rehash();
return true;
}
return false;
}
/** @return the number of elements in this set (its cardinality). */
public int size() {
checkRefQueue();
return size;
}
public T[] toArray(T[] array) {
ArrayList list = new ArrayList(array.length);
Iterator it = iterator();
while (it.hasNext()) {
list.add(it.next());
}
return list.toArray(array);
}
public Object[] toArray() {
ArrayList list = new ArrayList();
Iterator it = iterator();
while (it.hasNext()) {
list.add(it.next());
}
return list.toArray();
}
// #14772
public String toString() {
StringBuffer buf = new StringBuffer();
Iterator e = iterator();
buf.append("[");
while (e.hasNext()) {
buf.append(String.valueOf(e.next()));
if (e.hasNext()) {
buf.append(", ");
}
}
buf.append("]");
return buf.toString();
}
/** Checks if the queue is empty if not pending weak refs are removed. */
void checkRefQueue() {
for (;;) {
Entry entry = Entry.class.cast(refq.poll());
if (entry == null) {
break;
}
entry.remove();
size--;
}
}
/** @return modcount */
long modCount() {
return modcount;
}
/** @return an index to entries array */
int hashIt(Object o) {
return (o.hashCode() & 0x7fffffff) % entries.length;
}
/** rehashes this Set */
void rehash() {
/*
float currentLF = ((float) size) / ((float) entries.length);
if (currentLF < loadFactor) {
return;
}
*/
}
/** @return an Entry with given object */
private Entry object2Entry(Object o) {
checkRefQueue(); // clear ref q
int hash = hashIt(o);
Entry e = entries[hash];
if (e == null) {
return null;
}
while ((e != null) && !e.equals(o)) {
e = e.next;
}
return e;
}
private void writeObject(ObjectOutputStream obtos)
throws IOException {
obtos.defaultWriteObject();
obtos.writeObject(toArray());
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream obtis) throws IOException, ClassNotFoundException {
obtis.defaultReadObject();
Object[] arr = (Object[]) obtis.readObject();
entries = new Entry[(int) (size * 1.5)];
refq = new ReferenceQueue();
for (int i = 0; i < arr.length; i++) {
add((E)arr[i]);
}
}
class WeakSetIterator implements Iterator {
Entry current;
Entry next;
E currentObj;
E nextObj;
final long myModcount;
long myNullCount;
WeakSetIterator() {
myModcount = modCount();
myNullCount = nullCount;
current = null;
next = null;
Entry ee = iterChain;
if (ee == null) {
return;
}
E o = ee.get();
while (ee.isEnqueued()) {
ee = ee.iterChainNext;
if (ee == null) {
return;
}
o = ee.get();
}
nextObj = o;
next = ee;
}
public boolean hasNext() {
checkModcount();
return ((myNullCount > 0) || (next != null));
}
public E next() {
checkModcount();
checkRefQueue();
if (myNullCount > 0) {
myNullCount--;
return null;
} else {
if (next == null) {
throw new java.util.NoSuchElementException();
}
current = next;
currentObj = nextObj;
// move to next requested
do {
next = next.iterChainNext;
if (next == null) {
break;
}
nextObj = next.get();
} while (next.isEnqueued());
return currentObj;
}
}
public void remove() {
checkModcount();
if (current == null) {
throw new IllegalStateException();
}
current.remove();
size--;
}
void checkModcount() {
if (myModcount != modCount()) {
throw new ConcurrentModificationException();
}
}
}
/** Entries of this set */
static class Entry extends WeakReference {
/** reference to outer WeakSet */
private WeakSet set;
// double linked list
Entry prev;
Entry next;
private final int hashcode;
Entry iterChainNext;
Entry iterChainPrev;
Entry(WeakSet set, E referenced, ReferenceQueue q, Entry next, Entry nextInIter) {
super(referenced, q);
this.set = set;
this.next = next;
this.prev = null;
if (next != null) {
next.prev = this;
}
if (referenced != null) {
hashcode = set.hashIt(referenced);
} else {
hashcode = 0;
}
chainIntoIter(nextInIter);
}
@SuppressWarnings("unchecked")
static final Entry[] createArray(int size) {
return new Entry[size];
}
void chainIntoIter(Entry nextInIter) {
iterChainNext = nextInIter;
if (nextInIter != null) {
nextInIter.iterChainPrev = this;
}
}
/** deques itself */
void remove() {
if (prev != null) {
prev.next = next;
}
if (next != null) {
next.prev = prev;
}
if (iterChainNext != null) {
iterChainNext.iterChainPrev = iterChainPrev;
}
if (iterChainPrev != null) {
iterChainPrev.iterChainNext = iterChainNext;
} else { // root
set.iterChain = iterChainNext;
}
if (set.entries[hashcode] == this) {
set.entries[hashcode] = next;
}
prev = null;
next = null;
iterChainNext = null;
iterChainPrev = null;
}
public int hashCode() {
return hashcode;
}
public boolean equals(Object o) {
Object oo = get();
if (oo == null) {
return false;
} else {
return oo.equals(o);
}
}
public Entry clone(ReferenceQueue q) {
return new Entry(set, get(), q, next != null ? next.clone(q) : null, null);
}
}
}