/**
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.model.util;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
/**
* A container implementation that uses copy-on-write semantics to ensure that it is
* safe to mutate the set while iterating. The iterator semantics are
* equivalent to iterating though a snapshot taking at the time of calling
* {@link #iterator()}; however, a copy-on-write strategy is better suited for
* infrequently-modified but frequently-iterated container.
*
* A minor optimization to copy-on-write is that the underlying container is only
* copied if an iterator has previously been created for it. This means that,
* during a single iteration over a set of size N, a sequence of M operations
* only costs O(N + M) rather than O(NM) (i.e., the first mutation does the
* O(N) copy, and subsequent mutations do direct O(1) operations until another
* iterator is created).
*
* NOTE(user): This class is not synchronized.
*
*/
public final class CopyOnWriteSet<T> implements Iterable<T> {
/** Factory for blah. */
interface CollectionFactory {
/** @return a copy of a collection. */
<T> Collection<T> copy(Collection<T> source);
}
private final static CollectionFactory HASH_SET = new CollectionFactory() {
@Override
public <T> Collection<T> copy(Collection<T> source) {
// HACK(zdwang): We should use a light weight container that does
// not assume order when we feel confident enough that no one
// have implicit reliance on order of iteration.
return new LinkedHashSet<T>(source);
}
};
private final static CollectionFactory LIST_SET = new CollectionFactory() {
@Override
public <T> Collection<T> copy(Collection<T> source) {
return CollectionUtils.newArrayList(source);
}
};
/** Factory for the underlying collection object. */
private final CollectionFactory factory;
/** The base collection. Initially refers to a shared empty collection. */
private Collection<T> contents = Collections.emptySet();
/** True iff a copy is to be made on the next mutation. */
private boolean stale = true;
@VisibleForTesting
CopyOnWriteSet(CollectionFactory factory) {
this.factory = factory;
}
/** @return a new copy-on-write set, with the default implementation. */
public static <T> CopyOnWriteSet<T> create() {
return createHashSet();
}
/** @return a new copy-on-write set, backed by a hash set. */
public static <T> CopyOnWriteSet<T> createHashSet() {
return new CopyOnWriteSet<T>(HASH_SET);
}
/** @return a new copy-on-write set, backed by an array list. */
public static <T> CopyOnWriteSet<T> createListSet() {
return new CopyOnWriteSet<T>(LIST_SET);
}
/**
* Replaces the current set with a copy of it.
*/
private void copy() {
assert stale;
contents = factory.copy(contents);
stale = false;
}
/**
* Adds an item to this set.
*
* @param o object to add
* @return whether the container changed due to the addition
*/
public boolean add(T o) {
if (!stale) {
return contents.add(o);
} else {
if (!contains(o)) {
copy();
return contents.add(o);
} else {
return false;
}
}
}
/**
* Removes an item from this set.
*
* @param o object to remove
* @return whether the container changed due to the removal
*/
public boolean remove(T o) {
if (!stale) {
return contents.remove(o);
} else {
if (contains(o)) {
copy();
return contents.remove(o);
} else {
return false;
}
}
}
/**
* Checks whether an object exists in this collection.
*
* @param o object to check for existence
*/
public boolean contains(T o) {
return contents.contains(o);
}
@Override
public Iterator<T> iterator() {
stale = true;
return contents.iterator();
}
/**
* Clears this collection.
*/
public void clear() {
contents = Collections.emptySet();
stale = true;
}
/**
* @return true if this collection is empty.
*/
public boolean isEmpty() {
return contents.isEmpty();
}
/**
* @return the size of this collection.
*/
public int size() {
return contents.size();
}
@Override
public String toString() {
return contents.toString();
}
}