// // $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 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, 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[])}.
*/
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 [set=" + this +
", entry=" + elem + "].");
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 " +
"[length=" + 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)
{
// look up this entry's position in our set
int eidx = ArrayUtil.binarySearch(
_entries, 0, _size, 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