package com.github.obourgain.elasticsearch.http.concurrent;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
/**
* This class have similar properties than {@link CopyOnWriteArrayList} but allows to get a consistent snapshot of the collection.
* It supports multiple writers through CAS and retry-loop, but this may be more costly than a lock if the mutation rate is high.
*
* This does not give any guarantee regarding mutability of contained objects.
*
* @param <E>
*/
public class SnapshotableCopyOnWriteArray<E> {
private static final Object[] EMPTY = new Object[0];
protected AtomicReference<Object[]> array = new AtomicReference<>(EMPTY);
public SnapshotableCopyOnWriteArray() {
}
public SnapshotableCopyOnWriteArray(Object[] array) {
this.array.set(array);
}
public SnapshotableCopyOnWriteArray(Collection<E> collection) {
Object[] objects = collection.toArray();
this.array.set(objects);
}
public int size() {
return array.get().length;
}
public List<E> snapshot() {
return (List<E>) Arrays.asList(array.get());
}
public void add(E e) {
while (true) {
Object[] elements = array.get();
Object[] newElements = Arrays.copyOf(elements, elements.length + 1);
newElements[elements.length] = e;
if (array.compareAndSet(elements, newElements)) {
return;
}
}
}
public void remove(Object o) {
while (true) {
Object[] elements = array.get();
Object[] newElements = new Object[elements.length - 1]; // we expect the element to be present, so eagerly allocate a smaller array
for (int i = 0; i < elements.length ; i++) {
Object element = elements[i];
// last element is special case as we it is either :
// * the object to remove and then we don't need to copy it
// * it is not and so this collection does not contains the object to remove so we don't need to install the new array
if(i == elements.length - 1) {
if(Objects.equals(elements[elements.length - 1], o)) {
// skip copying this element and try to install the new array
break;
} else {
// this is not the droid you are looking for, just return
return;
}
}
if (Objects.equals(element, o)) {
// skip this element and copy the remaining elements
System.arraycopy(elements, i + 1, newElements, i, elements.length - i - 1);
break;
} else {
// copy to new array
newElements[i] = element;
}
}
if (array.compareAndSet(elements, newElements)) {
return;
}
}
}
public void addAll(Collection<? extends E> c) {
while (true) {
Object[] elements = array.get();
int currentLength = elements.length;
Object[] newElements = Arrays.copyOf(elements, currentLength + c.size());
int index = 0;
for (E e : c) {
newElements[currentLength + index] = e;
index++;
}
if (array.compareAndSet(elements, newElements)) {
return;
}
}
}
public void clear() {
this.array.set(EMPTY);
}
}