/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the LICENSE file that accompanied
* this code.
*
* --
*/
package com.sun.sgs.app.util;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedObjectRemoval;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.ObjectNotFoundException;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
/**
* A scalable implementation of {@code Set} backed by a {@link
* ScalableHashMap}. The internal structure of the set is separated into
* distributed pieces, which reduces the amount of data any one operation needs
* to access. The distributed structure increases the concurrency and allows
* for parallel write operations to successfully complete.
*
* <p>
*
* Developers may use this class as a drop-in replacement for the {@link
* java.util.HashSet} class for use in a {@link ManagedObject}. A {@code
* HashSet} will typically perform better than this class when the number of
* mappings is small, the objects being stored are small, and minimal
* concurrency is required. As the size of the serialized {@code HashSet}
* increases, this class will perform significantly better. Developers are
* encouraged to profile the serialized size of their set to determine which
* implementation will perform better. Note that {@code HashSet} does not
* provide any concurrency for {@code Task}s running in parallel that attempt
* to modify the set at the same time, so this class may perform better in
* situations where multiple tasks need to modify the set concurrently, even if
* the total number of elements is small. Also note that, unlike {@code
* HashSet}, this class can be used to store {@code ManagedObject} instances
* directly.
*
* <p>
*
* This implementation requires that all non-{@code null} elements implement
* {@link Serializable}. Attempting to add elements to the set that do not
* implement {@code Serializable} will result in an {@link
* IllegalArgumentException} being thrown. If an element is an instance of
* {@code Serializable} but does not implement {@code ManagedObject}, this
* class will persist the element as necessary; when such an element is removed
* from the set, it is also removed from the {@code DataManager}. If an
* element is an instance of {@code ManagedObject}, the developer will be
* responsible for removing these objects from the {@code DataManager} when
* done with them. Developers should not remove these object from the {@code
* DataManager} prior to removing them from the set.
*
* <p>
*
* Applications must make sure that objects used as elements in sets of this
* class have {@code equals} and {@code hashCode} methods that return the same
* values after the elements have been serialized and deserialized. In
* particular, elements that use {@link Object#equals Object.equals} and {@link
* Object#hashCode Object.hashCode} will typically not be equal, and will have
* different hash codes, each time they are deserialized, and so are probably
* not suitable for use with this class.
*
* <p>
*
* This class marks itself for update as necessary; no additional calls to the
* {@link DataManager} are necessary when modifying the map. Developers do not
* need to call {@code markForUpdate} or {@code getForUpdate} on this set, as
* this will eliminate all the concurrency benefits of this class. However,
* calling {@code getForUpdate} or {@code markForUpdate} can be used if a
* operation needs to prevent all access to the set.
*
* <p>
*
* This class offers constant-time implementations of the {@code add}, {@code
* remove} and {@code contains} methods. Note that, unlike most collections,
* the {@code size} and {@code isEmpty} methods for this class are <em>not</em>
* constant-time operations. Because of the asynchronous nature of the set,
* these operations may require accessing all of the entries in the set.
*
* <p>
*
* An instance of {@code ScalableHashSet} offers one parameter for performance
* tuning: {@code minConcurrency}, which specifies the minimum number of write
* operations to support in parallel. This parameter acts as a hint to the
* backing map on how to perform resizing. As the set grows, the number of
* supported parallel operations will also grow beyond the specified minimum.
* Setting the minimum concurrency too high will waste space and time, while
* setting it too low will cause conflicts until the map grows sufficiently to
* support more concurrent operations.
*
* <p>
*
* Since the expected distribution of objects in the set is essentially random,
* the actual concurrency will vary. Developers are strongly encouraged to use
* hash codes that provide a normal distribution; a large number of collisions
* will likely reduce the performance.
*
* <p>
*
* <a name="iterator"></a> The {@code Iterator} for this class implements
* {@code Serializable}. A single iterator may be saved by different {@code
* ManagedObject} instances, which will create distinct copies of the original
* iterator. A copy starts its iteration from where the state of the original
* was at the time of the copy. However, each copy maintains a separate,
* independent state from the original and will therefore not reflect any
* changes to the original iterator. To share a single {@code Iterator}
* between multiple {@code ManagedObject} <i>and</i> have the iterator use a
* consistent view for each, the iterator should be contained within a shared
* {@code ManagedObject}.
*
* <p>
*
* The iterator do not throw {@link java.util.ConcurrentModificationException}.
* The iterator for this class is stable with respect to the concurrent changes
* to the associated collection, but may ignore additions and removals made to
* the set during iteration.
*
* <p>
*
* If a call to the {@link Iterator#next next} method on the iterator causes a
* {@link ObjectNotFoundException} to be thrown because the return value has
* been removed from the {@code DataManager}, the iterator will still have
* successfully moved to the next entry in its iteration. In this case, the
* {@link Iterator#remove remove} method may be called on the iterator to
* remove the current object even though that object could not be returned.
*
* <p>
*
* This class and its iterator implement all optional operations and support
* {@code null} elements. This set provides no guarantees on the order of
* elements when iterating.
*
* @param <E> the type of elements maintained by this set
*
* @see Object#hashCode Object.hashCode
* @see java.util.Set
* @see java.util.HashSet
* @see ScalableHashMap
* @see Serializable
* @see ManagedObject
*/
public class ScalableHashSet<E>
extends AbstractSet<E>
implements Serializable, ManagedObjectRemoval {
/** The version of the serialized form. */
private static final long serialVersionUID = 1;
/**
* The internal marker for whether an object is present in the set.
*/
private static final Marker PRESENT = new Marker();
/**
* The minor version number, which can be modified to note a compatible
* change to the data structure. Incompatible changes should be marked by
* a change to the serialVersionUID.
*
* @serial
*/
private final short minorVersion = 1;
/**
* The reference to the backing map for this set.
*
* @serial
*/
private final ManagedReference<ScalableHashMap<E, Marker>> map;
/**
* Creates an empty set; the backing {@code ScalableHashMap} has the
* default minimum concurrency ({@code 32}).
*
* @see ScalableHashMap#ScalableHashMap()
*/
public ScalableHashSet() {
map = AppContext.getDataManager().
createReference(new ScalableHashMap<E, Marker>());
}
/**
* Creates an empty set; the backing {@code ScalableHashMap} has the
* specified minimum concurrency.
*
* @param minConcurrency the minimum number of concurrent write operations
* to support
*
* @throws IllegalArgumentException if {@code minConcurrency} is
* not greater than zero
*
* @see ScalableHashMap#ScalableHashMap(int)
*/
public ScalableHashSet(int minConcurrency) {
map = AppContext.getDataManager().
createReference(new ScalableHashMap<E, Marker>(minConcurrency));
}
/**
* Creates a new set containing the elements in the specified collection.
* The new set will have the default minimum concurrency ({@code 32}).
*
* @param c the collection of elements to be added to the set
*
* @throws IllegalArgumentException if any elements contained in the
* argument are not {@code null} and do not implement {@code
* Serializable}
*/
public ScalableHashSet(Collection<? extends E> c) {
this();
addAll(c);
}
/**
* Adds the specified element to this set if it was not already present.
*
* @param e the element to be added
*
* @return {@code true} if the set did not already contain the specified
* element
*
* @throws IllegalArgumentException if the argument is not {@code null} and
* does not implement {@code Serializable}
*/
public boolean add(E e) {
return map.get().put(e, PRESENT) == null;
}
/**
* Removes all the elements in this set. Note that unlike {@code HashSet},
* this operation is <i>not</i> constant time. Clearing a set takes {@code
* O(n*log(n))} time.
*/
public void clear() {
map.get().clear();
}
/**
* Returns {@code true} if this set contains the specified element.
*
* @param o the element whose presence in the set is to be tested
*
* @return {@code true} if this set contains the specified element
*/
public boolean contains(Object o) {
return map.get().containsKey(o);
}
/**
* Returns {@code true} if this set contains no elements.
*
* @return {@code true} if this set contains no elements
*/
public boolean isEmpty() {
return map.get().isEmpty();
}
/**
* Returns a concurrent, {@code Serializable} {@code Iterator} over the
* elements in this set.
*
* @return an iterator over the elements in this set
*/
public Iterator<E> iterator() {
return map.get().keySet().iterator();
}
/**
* Removes the specified element from this set if it was present.
*
* @param o the element that should be removed from the set, if present
*
* @return {@code true} if the element was initially present in this set
*/
public boolean remove(Object o) {
return PRESENT.equals(map.get().remove(o));
}
/**
* Returns the number of elements in this set. Note that unlike {@code
* HashMap}, this is <i>not</i> a constant time operation. Determining the
* size of a set takes {@code O(n*log(n))}.
*
* @return the number of elements in this set
*/
public int size() {
return map.get().size();
}
/**
* Retains only the elements in this collection that are contained in the
* specified collection. In other words, removes from this collection all
* of its elements that are not contained in the specified collection. <p>
*
* This implementation iterates over this collection, checking each element
* returned by the iterator in turn to see if it's contained in the
* specified collection. If it's not so contained, it's removed from this
* collection with the iterator's {@code remove} method.
*
* @param c elements to be retained in this collection.
*
* @return {@code true} if this collection changed as a result of the
* call
* @throws NullPointerException if the specified collection is {@code null}
*
* @see #remove
* @see #contains
*/
@Override
public boolean retainAll(Collection<?> c) {
/*
* The AbstractCollection method doesn't currently do this check.
* -tjb@sun.com (10/10/2007)
*/
if (c == null) {
throw new NullPointerException("The argument must not be null");
}
return super.retainAll(c);
}
/**
* {@inheritDoc} <p>
*
* This implementation removes the underlying {@code ScalableHashMap}.
*/
public void removingObject() {
AppContext.getDataManager().removeObject(
map.get());
}
/**
* An internal marker class for storing as the value in the backing map.
* All marker objects are equivalent to ensure equality after
* deserialization without the added cost of write replacement.
*/
private static final class Marker implements Serializable {
private static final long serialVersionUID = 1;
public int hashCode() {
return 0;
}
public boolean equals(Object o) {
return o != null && o instanceof Marker;
}
}
}