All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jpaul.DataStructs.COWSetFactory Maven / Gradle / Ivy

Go to download

This library was originally developed by people listed at http://jpaul.sourceforge.net.

The newest version!
// 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 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); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy