// // $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved // http://www.threerings.net/code/narya/ // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.presents.dobj; import java.io.IOException; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Iterator; import com.samskivert.util.ArrayUtil; import com.samskivert.util.StringUtil; import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamable; import com.threerings.presents.Log; /** * The distributed set class provides a means by which an unordered set of objects can be * maintained as a distributed object field. Entries can be added to and removed from the set, * requests for which will generate events much like other distributed object fields. * *
Classes that wish to act as set entries must implement the {@link Entry} interface which
* extends {@link Streamable} and adds the requirement that the object provide a key which will be
* used to identify entry equality. Thus an entry is declared to be in a set of the object returned
* by that entry's {@link Entry#getKey} method is equal (using {@link Object#equals}) to the entry
* returned by the {@link Entry#getKey} method of some other entry in the set. Additionally, in the
* case of entry removal, only the key for the entry to be removed will be transmitted with the
* removal event to save network bandwidth. Lastly, the object returned by {@link Entry#getKey}
* must be a {@link Streamable} type.
*/
public class DSetgetKey() method returns a key
* that equals() the key returned by getKey() of the supplied
* entry. Returns false otherwise.
*/
public boolean contains (E elem)
{
return containsKey(elem.getKey());
}
/**
* Returns true if an entry in the set has a key that equals() the supplied
* key. Returns false otherwise.
*/
public boolean containsKey (Comparable key)
{
return get(key) != null;
}
/**
* Returns the entry that matches (getKey().equals(key)) the specified key or null
* if no entry could be found that matches the key.
*/
public E get (Comparable key)
{
// determine where we'll be adding the new element
int eidx = ArrayUtil.binarySearch(_entries, 0, _size, new KeyWrapper(key), ENTRY_COMP);
return (eidx < 0) ? null : _entries[eidx];
}
/**
* Returns an iterator over the entries of this set. It does not support modification (nor
* iteration while modifications are being made to the set). It should not be kept around as it
* can quickly become out of date.
*
* @deprecated
*/
@Deprecated
public Iteratorarray argument is null, an object array of sufficient size to contain all
* of the elements of this set will be created and returned.
*/
public E[] toArray (E[] array)
{
if (array == null) {
@SuppressWarnings("unchecked") E[] copy = (E[])new Entry[size()];
array = copy;
}
System.arraycopy(_entries, 0, array, 0, array.length);
return array;
}
/**
* @deprecated use {@link #toArray(Entry[])}.
*/
@Deprecated
public Object[] toArray (Object[] array)
{
@SuppressWarnings("unchecked") E[] casted = (E[])array;
return toArray(casted);
}
/**
* Adds the specified entry to the set. This should not be called directly, instead the
* associated addTo{Set}() method should be called on the distributed object that
* contains the set in question.
*
* @return true if the entry was added, false if it was already in the set.
*/
protected boolean add (E elem)
{
// determine where we'll be adding the new element
int eidx = ArrayUtil.binarySearch(_entries, 0, _size, elem, ENTRY_COMP);
// if the element is already in the set, bail now
if (eidx >= 0) {
Log.warning("Refusing to add duplicate entry [entry=" + elem + ", set=" + this + "].");
return false;
}
// convert the index into happy positive land
eidx = (eidx+1)*-1;
// expand our entries array if necessary
int elength = _entries.length;
if (_size >= elength) {
// sanity check
if (elength > 2048) {
Log.warning("Requested to expand to questionably large size [l=" + elength + "].");
Thread.dumpStack();
}
// create a new array and copy our data into it
@SuppressWarnings("unchecked") E[] elems = (E[])new Entry[elength*2];
System.arraycopy(_entries, 0, elems, 0, elength);
_entries = elems;
}
// if the entry doesn't go at the end, shift the elements down to accomodate it
if (eidx < _size) {
System.arraycopy(_entries, eidx, _entries, eidx+1, _size-eidx);
}
// stuff the entry into the array and note that we're bigger
_entries[eidx] = elem;
_size++;
_modCount++;
return true;
}
/**
* Removes the specified entry from the set. This should not be called directly, instead the
* associated removeFrom{Set}() method should be called on the distributed object
* that contains the set in question.
*
* @return true if the entry was removed, false if it was not in the set.
*/
protected boolean remove (E elem)
{
return (null != removeKey(elem.getKey()));
}
/**
* Removes from the set the entry whose key matches the supplied key. This should not be called
* directly, instead the associated removeFrom{Set}() method should be called on
* the distributed object that contains the set in question.
*
* @return the old matching entry if found and removed, null if not found.
*/
protected E removeKey (Comparable key)
{
// don't fail, but generate a warning if we're passed a null key
if (key == null) {
Log.warning("Requested to remove null key.");
Thread.dumpStack();
return null;
}
// look up this entry's position in our set
int eidx = ArrayUtil.binarySearch(_entries, 0, _size, new KeyWrapper(key), ENTRY_COMP);
// if we found it, remove it
if (eidx >= 0) {
// extract the old entry
E oldEntry = _entries[eidx];
_size--;
if ((_entries.length > INITIAL_CAPACITY) && (_size < _entries.length/8)) {
// if we're using less than 1/8 of our capacity, shrink by half
@SuppressWarnings("unchecked") E[] newEnts = (E[])new Entry[_entries.length/2];
System.arraycopy(_entries, 0, newEnts, 0, eidx);
System.arraycopy(_entries, eidx+1, newEnts, eidx, _size-eidx);
_entries = newEnts;
} else {
// shift entries past the removed one downwards
System.arraycopy(_entries, eidx+1, _entries, eidx, _size-eidx);
_entries[_size] = null;
}
_modCount++;
return oldEntry;
} else {
return null;
}
}
/**
* Updates the specified entry by locating an entry whose key matches the key of the supplied
* entry and overwriting it. This should not be called directly, instead the associated
* update{Set}() method should be called on the distributed object that contains
* the set in question.
*
* @return the old entry that was replaced, or null if it was not found (in which case nothing
* is updated).
*/
protected E update (E elem)
{
// look up this entry's position in our set
int eidx = ArrayUtil.binarySearch(_entries, 0, _size, elem, ENTRY_COMP);
// if we found it, update it
if (eidx >= 0) {
E oldEntry = _entries[eidx];
_entries[eidx] = elem;
_modCount++;
return oldEntry;
} else {
return null;
}
}
/**
* Generates a shallow copy of this object in a type safe manner.
*/
public DSet