Convert Narya (most of the way) over to a Maven Ant task based build. The
ActionScript bits remain belligerent, but the Java stuff is mostly shipshape. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6222 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,530 @@
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2010 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/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.util.AbstractSet;
|
||||
import java.util.Comparator;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.samskivert.util.ArrayUtil;
|
||||
|
||||
import com.threerings.io.ObjectInputStream;
|
||||
import com.threerings.io.ObjectOutputStream;
|
||||
import com.threerings.io.Streamable;
|
||||
|
||||
import static com.threerings.presents.Log.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.
|
||||
*
|
||||
* <p> 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.
|
||||
*
|
||||
* @param <E> the type of entry stored in this set.
|
||||
*/
|
||||
public class DSet<E extends DSet.Entry>
|
||||
implements Iterable<E>, Streamable, Cloneable
|
||||
{
|
||||
/**
|
||||
* Entries of the set must implement this interface.
|
||||
*/
|
||||
public static interface Entry extends Streamable
|
||||
{
|
||||
/**
|
||||
* Each entry provide an associated key which is used to determine its uniqueness in the
|
||||
* set. See the {@link DSet} class documentation for further information.
|
||||
*/
|
||||
Comparable<?> getKey ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DSet of the appropriate generic type.
|
||||
*/
|
||||
public static <E extends DSet.Entry> DSet<E> newDSet ()
|
||||
{
|
||||
return new DSet<E>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DSet of the appropriate generic type.
|
||||
*/
|
||||
public static <E extends DSet.Entry> DSet<E> newDSet (Iterable<E> source)
|
||||
{
|
||||
return new DSet<E>(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the first comparable to the second. This is useful to avoid type safety warnings
|
||||
* when dealing with the keys of {@link DSet.Entry} values.
|
||||
*/
|
||||
public static int compare (Comparable<?> c1, Comparable<?> c2)
|
||||
{
|
||||
@SuppressWarnings("unchecked") Comparable<Object> cc1 = (Comparable<Object>)c1;
|
||||
@SuppressWarnings("unchecked") Comparable<Object> cc2 = (Comparable<Object>)c2;
|
||||
return cc1.compareTo(cc2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a distributed set and populates it with values from the supplied iterator. This
|
||||
* should be done before the set is unleashed into the wild distributed object world because no
|
||||
* associated entry added events will be generated. Additionally, this operation does not check
|
||||
* for duplicates when adding entries, so one should be sure that the iterator contains only
|
||||
* unique entries.
|
||||
*
|
||||
* @param source an iterator from which we will initially populate the set.
|
||||
*/
|
||||
public DSet (Iterable<E> source)
|
||||
{
|
||||
for (E e : source) {
|
||||
add(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a distributed set and populates it with values from the supplied iterator. This
|
||||
* should be done before the set is unleashed into the wild distributed object world because no
|
||||
* associated entry added events will be generated. Additionally, this operation does not check
|
||||
* for duplicates when adding entries, so one should be sure that the iterator contains only
|
||||
* unique entries.
|
||||
*
|
||||
* @param source an iterator from which we will initially populate the set.
|
||||
*/
|
||||
public DSet (Iterator<E> source)
|
||||
{
|
||||
while (source.hasNext()) {
|
||||
add(source.next());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a distributed set and populates it with values from the supplied array. This should
|
||||
* be done before the set is unleashed into the wild distributed object world because no
|
||||
* associated entry added events will be generated. Additionally, this operation does not check
|
||||
* for duplicates when adding entries, so one should be sure that the iterator contains only
|
||||
* unique entries.
|
||||
*
|
||||
* @param source an array from which we will initially populate the set.
|
||||
*/
|
||||
public DSet (E[] source)
|
||||
{
|
||||
for (E element : source) {
|
||||
if (element != null) {
|
||||
add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty distributed set.
|
||||
*/
|
||||
public DSet ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of entries in this set.
|
||||
*/
|
||||
public int size ()
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the set contains an entry whose <code>getKey()</code> method returns a key
|
||||
* that <code>equals()</code> the key returned by <code>getKey()</code> 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 <code>equals()</code> the supplied
|
||||
* key. Returns false otherwise.
|
||||
*/
|
||||
public boolean containsKey (Comparable<?> key)
|
||||
{
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry that matches (<code>getKey().equals(key)</code>) 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 SimpleEntry<Comparable<?>>(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 Iterator<E> entries ()
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Iterator<E> iterator ()
|
||||
{
|
||||
// the crazy sanity checks
|
||||
if (_size < 0 || _size > _entries.length || (_size > 0 && _entries[_size-1] == null)) {
|
||||
log.warning("DSet in a bad way", "size", _size, "entries", _entries, new Exception());
|
||||
}
|
||||
|
||||
return new Iterator<E>() {
|
||||
public boolean hasNext () {
|
||||
checkComodification();
|
||||
return (_index < _size);
|
||||
}
|
||||
public E next () {
|
||||
checkComodification();
|
||||
return _entries[_index++];
|
||||
}
|
||||
public void remove () {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
protected void checkComodification () {
|
||||
if (_modCount != _expectedModCount) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
if (_ssize != _size) {
|
||||
log.warning("Size changed during iteration", "ssize", _ssize, "nsize", _size,
|
||||
"entries", _entries, new Exception());
|
||||
}
|
||||
}
|
||||
protected int _index = 0;
|
||||
protected int _ssize = _size;
|
||||
protected int _expectedModCount = _modCount;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the elements of this distributed set into the supplied array. If the array is not
|
||||
* large enough to hold all of the elements, as many as fit into the array will be copied. If
|
||||
* the <code>array</code> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an <b>immutable</b> view of this distributed set as a Java set.
|
||||
*/
|
||||
public Set<E> asSet ()
|
||||
{
|
||||
return new AbstractSet<E>() {
|
||||
@Override public boolean add (E o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override public boolean remove (Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@Override public boolean contains (Object o) {
|
||||
if (!(o instanceof DSet.Entry)) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked") E elem = (E)o;
|
||||
return DSet.this.contains(elem);
|
||||
}
|
||||
@Override public Iterator<E> iterator () {
|
||||
return DSet.this.iterator();
|
||||
}
|
||||
@Override public int size () {
|
||||
return DSet.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 <code>addTo{Set}()</code> 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,
|
||||
new Exception());
|
||||
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 > getWarningSize()) {
|
||||
log.warning("Requested to expand to questionably large size", "l", elength,
|
||||
new Exception());
|
||||
}
|
||||
|
||||
// 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 <code>removeFrom{Set}()</code> 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 <code>removeFrom{Set}()</code> 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.", new Exception());
|
||||
return null;
|
||||
}
|
||||
|
||||
// look up this entry's position in our set
|
||||
int eidx = ArrayUtil.binarySearch(
|
||||
_entries, 0, _size, new SimpleEntry<Comparable<?>>(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
|
||||
* <code>update{Set}()</code> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum size where we should warn that we're getting a bit large.
|
||||
*/
|
||||
protected int getWarningSize ()
|
||||
{
|
||||
return 2048;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a shallow copy of this object in a type safe manner.
|
||||
*
|
||||
* @deprecated clone() works just fine now.
|
||||
*/
|
||||
@Deprecated
|
||||
public DSet<E> typedClone ()
|
||||
{
|
||||
return clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a shallow copy of this object.
|
||||
*/
|
||||
@Override
|
||||
public DSet<E> clone ()
|
||||
{
|
||||
try {
|
||||
@SuppressWarnings("unchecked") DSet<E> nset = (DSet<E>)super.clone();
|
||||
@SuppressWarnings("unchecked") E[] copy = (E[])new Entry[_entries.length];
|
||||
nset._entries = copy;
|
||||
System.arraycopy(_entries, 0, nset._entries, 0, _entries.length);
|
||||
nset._modCount = 0;
|
||||
return nset;
|
||||
} catch (CloneNotSupportedException cnse) {
|
||||
throw new AssertionError(cnse);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString ()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder("(");
|
||||
String prefix = "";
|
||||
for (E elem : _entries) {
|
||||
if (elem != null) {
|
||||
buf.append(prefix);
|
||||
prefix = ", ";
|
||||
buf.append(elem);
|
||||
}
|
||||
}
|
||||
buf.append(")");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** Custom writer method. @see Streamable. */
|
||||
public void writeObject (ObjectOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
out.writeInt(_size);
|
||||
for (int ii = 0; ii < _size; ii++) {
|
||||
out.writeObject(_entries[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Custom reader method. @see Streamable. */
|
||||
public void readObject (ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
_size = in.readInt();
|
||||
// ensure our capacity is a power of 2 (for consistency)
|
||||
int capacity = INITIAL_CAPACITY;
|
||||
while (capacity < _size) {
|
||||
capacity <<= 1;
|
||||
}
|
||||
@SuppressWarnings("unchecked") E[] entries = (E[])new Entry[capacity];
|
||||
_entries = entries;
|
||||
for (int ii = 0; ii < _size; ii++) {
|
||||
@SuppressWarnings("unchecked") E entry = (E)in.readObject();
|
||||
_entries[ii] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
/** The entries of the set (in a sparse array). */
|
||||
@SuppressWarnings("unchecked") protected E[] _entries = (E[])new Entry[INITIAL_CAPACITY];
|
||||
|
||||
/** The number of entries in this set. */
|
||||
protected int _size;
|
||||
|
||||
/** Used to check for concurrent modification. */
|
||||
protected transient int _modCount;
|
||||
|
||||
/** The default capacity of a set instance. */
|
||||
protected static final int INITIAL_CAPACITY = 2;
|
||||
|
||||
/** Used for lookups and to keep the set contents sorted on insertions. */
|
||||
protected static Comparator<Entry> ENTRY_COMP = new Comparator<Entry>() {
|
||||
public int compare (Entry e1, Entry e2) {
|
||||
return DSet.compare(e1.getKey(), e2.getKey());
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user