jpaul.DataStructs.COWSetFactory Maven / Gradle / Ivy
Show all versions of jpaul Show documentation
// COWSetFactory.java, created Thu Jul 14 13:33:53 2005 by salcianu
// Copyright (C) 2005 Alexandru Salcianu
// Licensed under the Modified BSD Licence; see COPYING for details.
package jpaul.DataStructs;
import java.util.Set;
import java.util.Collection;
import java.util.Iterator;
/**
COWSetFactory
generates "copy-on-write" (COW) sets. A
COW set shares its representation (also a set) with other COW sets,
until a mutation occurs. At that moment, the COW set makes a
private, exclusive copy of its underlying representation, and
mutates that copy.
The internal representation of a COW set maintains a "sharing"
counter to identify cases when the representation is not shared
with anyone (and hence, no cloning is necessary before a mutation).
Cloning a COW set is a constant time operation. COW sets are
good when it is hard to determine statically whether a clone of a
set will be mutated: they delay the real cloning until the first
mutation (if any).
Note: COW sets are NOT thread-safe. If you
need thread-safety, use a synchronization wrapper, e.g.,
Collections.synchronizedSet
.
* @author Alexandru Salcianu - [email protected]
* @version $Id: COWSetFactory.java,v 1.12 2006/03/23 15:50:46 adam_kiezun Exp $ */
class COWSetFactory extends SetFactory {
private static final long serialVersionUID = 2750791597157055447L;
/** Creates a COWSetFactory
.
@param underSetFact Set factory for generating the sets used
in the representation of the COW sets generated by this
COWSetFactory
. */
public COWSetFactory(SetFactory underSetFact) {
this.underSetFact = underSetFact;
}
private SetFactory underSetFact;
public Set create() {
return new COWSet(underSetFact);
}
public Set newColl(Collection coll) {
if(coll instanceof COWSet/**/) {
return ((COWSet) coll).clone();
}
return super.newColl(coll);
}
private static class SetWithCount {
SetWithCount(Set set) {
this.set = set;
}
int countOthers = 0;
final Set set;
}
private static class COWSet implements Set, Cloneable {
/** Creates a COWSet
object.
@param underSetFact Set factory for creating the
underlying, shared set. */
COWSet(SetFactory underSetFact) {
this.underSetFact = underSetFact;
this.underSet = new SetWithCount(underSetFact.create());
}
private final SetFactory underSetFact;
private SetWithCount underSet;
/* Synchronization: we maintain the following invariants:
I1. If a mutation operation is invoked on the underlying
set underSet.set, then during the the entire execution of
the operation, the sharing count underSet.countOthers is 0.
I2. At any point during the program execution, if n
reachable (= not collected by the GC) COWSets point to the
same underlying SetWithCount swc, then swc.countOthers is
at least n-1.
I2 makes sure that if I1 is valid, then any underlying set
that we mutate is "owned" by a single top-level COWSet.
We maintain I2 as follows:
(1) every time we create new sharing of underSet (i.e., in
clone()), we first increment the sharing count and next
create new sharing.
(2) every time sharing is destroyed (i.e., in detach(), or
during GC), we first destroy sharing, and next decrement
the sharing count.
I1 is more delicate. However, notice how we always detach
the underlying set before a mutation UNLESS the sharing
count is already 0 (i.e., the COWSet has its own, unshared
underSet). Notice that there is no atomicity of the
sequences that tests the sharing counter and next invokes
the actual mutation. If a clone() operation "sneaks in
between", we may have a nasty surprise. That's why in a
multi-thread program, one should wrap each COWSet in a
thread-safe Collections.synchronizedSet. This way, if one
thread executes an operation on a COWSet and decides not to
detach its underSet, then it knows that (1) this is the
only COWSet that uses that underSet, and (2) since the
top-level COWSet operation holds the lock on that COWSet,
no other thread can call clone() before the current
operation is finished.
The only syncs that appear in the code below make sure
that each increment / decrement occurs atomically (I don't
think the JVM guarantees this; better safe than sorry).
Final note:
Q: Why do we need a finalizer for COWSet?
A: To decrement the count of the underlying shared set. If
we don't do so, the underlying set may seem (artficially)
to be shared, leading to unnecessary copying during the
next mutation operation.
*/
public boolean add(E elem) {
if(underSet.countOthers != 0) {
if(underSet.set.contains(elem)) {
return false;
}
else {
detach();
}
}
return underSet.set.add(elem);
}
public boolean addAll(Collection extends E> c) {
if(c instanceof COWSet/**/) {
COWSet cowSet2 = (COWSet) c;
// we just add a set to itself !
if(this.underSet == cowSet2.underSet)
return false;
}
if(underSet.countOthers != 0) {
if(underSet.set.containsAll(c)) {
return false;
}
else {
detach();
}
}
return underSet.set.addAll(c);
}
public void clear() {
if(underSet.countOthers != 0) {
if(underSet.set.isEmpty()) {
return;
}
else {
detach();
}
}
underSet.set.clear();
}
public boolean contains(Object o) {
return underSet.set.contains(o);
}
public boolean containsAll(Collection> c) {
return underSet.set.containsAll(c);
}
public boolean equals(Object o) {
if(o == null) return false;
if(o == this) return true;
if(o instanceof COWSet/**/) {
@SuppressWarnings("unchecked")
COWSet cowSet2 = (COWSet) o;
// hopefully, this is a common case :)
if(this.underSet == cowSet2.underSet)
return true;
return this.underSet.set.equals(cowSet2.underSet.set);
}
return this.underSet.set.equals(o);
}
public int hashCode() { return underSet.set.hashCode(); }
public boolean isEmpty() { return underSet.set.isEmpty(); }
public boolean remove(Object o) {
if(underSet.countOthers != 0) {
if(underSet.set.contains(o)) {
detach();
}
else {
return false;
}
}
return underSet.set.remove(o);
}
public boolean removeAll(Collection> c) {
if(underSet.countOthers != 0) {
detach();
}
return underSet.set.removeAll(c);
}
public boolean retainAll(Collection> c) {
throw new UnsupportedOperationException();
}
public int size() { return underSet.set.size(); }
public Object[] toArray() { return underSet.set.toArray(); }
public T[] toArray(T[] a) { return underSet.set.toArray(a); }
public COWSet clone() {
try {
synchronized(underSet) {
underSet.countOthers++;
}
// clone will also copy the reference to the underlying SetWithCount
// We've already incremented the sharing count, so we preserve I2.
@SuppressWarnings("unchecked")
COWSet cloneObj = (COWSet) super.clone();
return cloneObj;
}
catch(CloneNotSupportedException cex) {
// should not happen
throw new Error(cex);
}
}
public Iterator iterator() {
return DSUtil.unmodifiableIterator(underSet.set.iterator());
}
private void detach() {
SetWithCount oldUnderSet = this.underSet;
this.underSet = new SetWithCount(underSetFact.create(oldUnderSet.set));
// do the decrement HERE, not before the real detach, to maintain I2
synchronized(oldUnderSet) {
oldUnderSet.countOthers--;
}
}
public String toString() {
if(underSet.countOthers == 0) {
return underSet.set.toString();
}
return
"(shared: " + (underSet.countOthers+1) + ") ; " +
underSet.set.toString();
}
protected void finalize() {
try {
try {
synchronized(underSet) {
underSet.countOthers--;
}
}
finally {
super.finalize();
}
}
catch(Throwable tex) {
throw new Error(tex);
}
}
}
}