/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.collection;
import java.io.Serializable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* THIS CLASS IS INTENDED TO BE USED INTERNALLY ONLY!
* <p>
* This helper class provides methods that return views on Java collections.
* The view creation and back conversion take O(1).
*
* @author Daniel Dietrich
*/
class JavaConverters {
private JavaConverters() {
}
@GwtIncompatible
static <T, C extends Seq<T>> ListView<T, C> asJava(C seq, ChangePolicy changePolicy) {
return new ListView<>(seq, changePolicy.isMutable());
}
enum ChangePolicy {
IMMUTABLE, MUTABLE;
boolean isMutable() {
return this == MUTABLE;
}
}
// -- private view implementations
/**
* Encapsulates the access to delegate and performs mutability checks.
*
* @param <C> The Vavr collection type
*/
private static abstract class HasDelegate<C extends Traversable<?>> implements Serializable {
private static final long serialVersionUID = 1L;
private C delegate;
private final boolean mutable;
HasDelegate(C delegate, boolean mutable) {
this.delegate = delegate;
this.mutable = mutable;
}
protected boolean isMutable() {
return mutable;
}
C getDelegate() {
return delegate;
}
protected boolean setDelegateAndCheckChanged(Supplier<C> delegate) {
ensureMutable();
final C previousDelegate = this.delegate;
final C newDelegate = delegate.get();
final boolean changed = newDelegate.size() != previousDelegate.size();
if (changed) {
this.delegate = newDelegate;
}
return changed;
}
protected void setDelegate(Supplier<C> newDelegate) {
ensureMutable();
this.delegate = newDelegate.get();
}
protected void ensureMutable() {
if (!mutable) {
throw new UnsupportedOperationException();
}
}
}
@GwtIncompatible("reflection is not supported")
static class ListView<T, C extends Seq<T>> extends HasDelegate<C> implements java.util.List<T> {
private static final long serialVersionUID = 1L;
ListView(C delegate, boolean mutable) {
super(delegate, mutable);
}
@SuppressWarnings("unchecked")
@Override
public boolean add(T element) {
setDelegate(() -> (C) getDelegate().append(element));
return true;
}
@SuppressWarnings("unchecked")
@Override
public void add(int index, T element) {
setDelegate(() -> (C) getDelegate().insert(index, element));
}
@SuppressWarnings("unchecked")
@Override
public boolean addAll(Collection<? extends T> collection) {
Objects.requireNonNull(collection, "collection is null");
return setDelegateAndCheckChanged(() -> (C) getDelegate().appendAll(collection));
}
@SuppressWarnings("unchecked")
@Override
public boolean addAll(int index, Collection<? extends T> collection) {
Objects.requireNonNull(collection, "collection is null");
return setDelegateAndCheckChanged(() -> (C) getDelegate().insertAll(index, collection));
}
@SuppressWarnings("unchecked")
@Override
public void clear() {
// DEV-NOTE: acts like Java: works for empty immutable collections
if (isEmpty()) {
return;
}
setDelegate(() -> (C) getDelegate().take(0));
}
@Override
public boolean contains(Object obj) {
@SuppressWarnings("unchecked") final T that = (T) obj;
return getDelegate().contains(that);
}
@Override
public boolean containsAll(Collection<?> collection) {
Objects.requireNonNull(collection, "collection is null");
@SuppressWarnings("unchecked") final Collection<T> that = (Collection<T>) collection;
return getDelegate().containsAll(that);
}
@Override
public T get(int index) {
return getDelegate().get(index);
}
@Override
public int indexOf(Object obj) {
@SuppressWarnings("unchecked") final T that = (T) obj;
return getDelegate().indexOf(that);
}
@Override
public boolean isEmpty() {
return getDelegate().isEmpty();
}
@Override
public java.util.Iterator<T> iterator() {
return new Iterator<>(this);
}
@Override
public int lastIndexOf(Object obj) {
@SuppressWarnings("unchecked") final T that = (T) obj;
return getDelegate().lastIndexOf(that);
}
@Override
public java.util.ListIterator<T> listIterator() {
return new ListIterator<>(this, 0);
}
@Override
public java.util.ListIterator<T> listIterator(int index) {
return new ListIterator<>(this, index);
}
@SuppressWarnings("unchecked")
@Override
public T remove(int index) {
return setDelegateAndGetPreviousElement(index, () -> (C) getDelegate().removeAt(index));
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object obj) {
final T that = (T) obj;
return setDelegateAndCheckChanged(() -> (C) getDelegate().remove(that));
}
@SuppressWarnings("unchecked")
@Override
public boolean removeAll(Collection<?> collection) {
Objects.requireNonNull(collection, "collection is null");
@SuppressWarnings("unchecked") final Collection<T> that = (Collection<T>) collection;
return setDelegateAndCheckChanged(() -> (C) getDelegate().removeAll(that));
}
@SuppressWarnings("unchecked")
@Override
public boolean retainAll(Collection<?> collection) {
Objects.requireNonNull(collection, "collection is null");
@SuppressWarnings("unchecked") final Collection<T> that = (Collection<T>) collection;
return setDelegateAndCheckChanged(() -> (C) getDelegate().retainAll(that));
}
@SuppressWarnings("unchecked")
@Override
public T set(int index, T element) {
return setDelegateAndGetPreviousElement(index, () -> (C) getDelegate().update(index, element));
}
@Override
public int size() {
return getDelegate().size();
}
@SuppressWarnings("unchecked")
@Override
public void sort(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator, "comparator is null");
if (isEmpty()) {
return;
}
setDelegate(() -> (C) getDelegate().sorted(comparator));
}
@Override
public java.util.List<T> subList(int fromIndex, int toIndex) {
return new ListView<>(getDelegate().subSequence(fromIndex, toIndex), isMutable());
}
@Override
public Object[] toArray() {
return getDelegate().toJavaArray();
}
@SuppressWarnings("unchecked")
@Override
public <U> U[] toArray(U[] array) {
Objects.requireNonNull(array, "array is null");
final U[] target;
final C delegate = getDelegate();
final int length = delegate.length();
if (array.length < length) {
final Class<? extends Object[]> newType = array.getClass();
target = (newType == Object[].class)
? (U[]) new Object[length]
: (U[]) java.lang.reflect.Array.newInstance(newType.getComponentType(), length);
} else {
if (array.length > length) {
array[length] = null;
}
target = array;
}
final java.util.Iterator<T> iter = delegate.iterator();
for (int i = 0; i < length; i++) {
target[i] = (U) iter.next();
}
return target;
}
// -- Object.*
@Override
public boolean equals(Object o) {
return o == this || o instanceof java.util.List && Collections.areEqual(getDelegate(), (java.util.List<?>) o);
}
@Override
public int hashCode() {
// DEV-NOTE: Ensures that hashCode calculation is stable, regardless of delegate.hashCode()
return Collections.hashOrdered(getDelegate());
}
@Override
public String toString() {
return getDelegate().mkString("[", ", ", "]");
}
// -- private helpers
private T setDelegateAndGetPreviousElement(int index, Supplier<C> delegate) {
ensureMutable();
final T previousElement = getDelegate().get(index);
setDelegate(delegate);
return previousElement;
}
// DEV-NOTE: Iterator is intentionally not Serializable
private static class Iterator<T, C extends Seq<T>> implements java.util.Iterator<T> {
ListView<T, C> list;
int expectedSize;
int nextIndex = 0;
int lastIndex = -1;
Iterator(ListView<T, C> list) {
this.list = list;
expectedSize = list.size();
}
@Override
public boolean hasNext() {
return nextIndex != list.size();
}
@Override
public T next() {
checkForComodification();
if (nextIndex >= list.size()) {
throw new NoSuchElementException();
}
try {
return list.get(lastIndex = nextIndex++);
} catch (IndexOutOfBoundsException x) {
throw new ConcurrentModificationException();
}
}
@Override
public void remove() {
list.ensureMutable();
if (lastIndex < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
list.remove(nextIndex = lastIndex);
lastIndex = -1;
expectedSize = list.size();
} catch (IndexOutOfBoundsException x) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer<? super T> consumer) {
Objects.requireNonNull(consumer, "consumer is null");
checkForComodification();
if (nextIndex >= list.size()) {
return;
}
int index = nextIndex;
// DEV-NOTE: intentionally not using hasNext() and next() in order not to modify internal state
while (expectedSize == list.size() && index < expectedSize) {
consumer.accept(list.get(index++));
}
nextIndex = index;
lastIndex = index - 1;
checkForComodification();
}
final void checkForComodification() {
if (expectedSize != list.size()) {
throw new ConcurrentModificationException();
}
}
}
// DEV-NOTE: ListIterator is intentionally not Serializable
private static class ListIterator<T, C extends Seq<T>> extends ListView.Iterator<T, C> implements java.util.ListIterator<T> {
ListIterator(ListView<T, C> list, int index) {
super(list);
if (index < 0 || index > list.size()) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + list.size());
}
this.nextIndex = index;
}
@Override
public boolean hasPrevious() {
return nextIndex != 0;
}
@Override
public int nextIndex() {
return nextIndex;
}
@Override
public int previousIndex() {
return nextIndex - 1;
}
@Override
public T previous() {
checkForComodification();
final int index = nextIndex - 1;
if (index < 0) {
throw new NoSuchElementException();
}
if (index >= list.size()) {
throw new ConcurrentModificationException();
}
try {
final T element = list.get(index);
// DEV-NOTE: intentionally updating indices _after_ reading the element. This makes a difference in case of a concurrent modification.
lastIndex = nextIndex = index;
return element;
} catch (IndexOutOfBoundsException x) {
throw new ConcurrentModificationException();
}
}
@Override
public void set(T element) {
list.ensureMutable();
if (lastIndex < 0) {
throw new IllegalStateException();
}
checkForComodification();
try {
list.set(lastIndex, element);
} catch (IndexOutOfBoundsException x) {
throw new ConcurrentModificationException();
}
}
@Override
public void add(T element) {
list.ensureMutable();
checkForComodification();
try {
final int index = nextIndex;
list.add(index, element);
// DEV-NOTE: intentionally increasing nextIndex _after_ adding the element. This makes a difference in case of a concurrent modification.
nextIndex = index + 1;
lastIndex = -1;
expectedSize = list.size();
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
}