Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions reactfx/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ apply plugin: 'signing'
group = 'org.reactfx'

dependencies {
compile group: 'com.google.guava', name: 'guava', version: '18.0'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
testCompile group: 'org.junit.contrib', name: 'junit-theories', version: '4.12'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,17 @@ public int previousIndex() {

@Override
public void remove() {
throw new UnsupportedOperationException();
list.remove(position--);
}

@Override
public void set(E e) {
throw new UnsupportedOperationException();
list.set(position - 1, e);
}

@Override
public void add(E e) {
throw new UnsupportedOperationException();
list.add(position++, e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;

import org.reactfx.Subscription;

public final class LiveArrayList<E> extends LiveListBase<E> {
public final class LiveArrayList<E> extends LiveListBase<E> implements RandomAccess {
private List<E> list;

public LiveArrayList() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
package org.reactfx.collection;

import static java.util.Collections.binarySearch;
import static java.util.Collections.emptySortedSet;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.collections.SetChangeListener;

import com.google.common.collect.Sets;
import org.reactfx.Subscription;

/**
* Implementation of {@link ObservableSortedSet} based on {@link ArrayList}.
*/
public final class ObservableSortedArraySet<E> extends AbstractSet<E> implements ObservableSortedSet<E> {
private final LiveList<E> backing = new LiveArrayList<>();
private final Map<E, Observable[]> observables = new IdentityHashMap<>();
private final Collection<SetChangeListener<? super E>> observers = new CopyOnWriteArrayList<>();
private final Collection<InvalidationListener> invalidationListeners = new CopyOnWriteArrayList<>();
private final Comparator<? super E> comparator;
private final Function<? super E, ? extends Collection<? extends Observable>> resortListenFunction;
private final InvalidationListener resortListener, resortListenerWeak;
private final LiveList<E> listView = new ListView();

private final class ListView extends LiveListBase<E> implements ReadOnlyLiveListImpl<E> {
@Override
public int size() {
return backing.size();
}

@Override
public E get(int index) {
return backing.get(index);
}

@Override
protected Subscription observeInputs() {
return LiveList.<E>observeQuasiChanges(backing, this::notifyObservers);
}
}

@Override
public LiveList<E> listView() {
return listView;
}

/**
* Constructs a new {@link ObservableSortedArraySet}.
*
* <p>The {@code resortListenFunction} parameter takes a function that,
* given a value stored in the set, yields any number of
* {@link Observable}s. Whenever any of them are
* {@linkplain Observable#addListener(InvalidationListener) invalidated},
* this set is resorted. This way, the sort order of the items in the set
* (and its {@linkplain #listView list view}) are kept up to date.</p>
*
* @param comparator how the items in the set will be compared
* @param resortListenFunction triggers for re-sorting, as above
*/
public ObservableSortedArraySet(Comparator<? super E> comparator, Function<? super E, ? extends Collection<? extends Observable>> resortListenFunction) {
this.comparator = comparator;
this.resortListenFunction = resortListenFunction;

resortListener = obs -> resort();
resortListenerWeak = new WeakInvalidationListener(resortListener);
}

private void resort() {
backing.sort(comparator);
}

private void onAdded(E o) {
Observable[] os = observables.computeIfAbsent(o, oo -> {
Collection<? extends Observable> osc = resortListenFunction.apply(oo);
return osc.toArray(new Observable[osc.size()]);
});

for (Observable oo : os) {
oo.addListener(resortListenerWeak);
}

fire(new SetChangeListener.Change<E>(this) {
@Override
public boolean wasAdded() {
return true;
}

@Override
public boolean wasRemoved() {
return false;
}

@Override
public E getElementAdded() {
return o;
}

@Override
public E getElementRemoved() {
return null;
}
});
}

private void onRemoved(E o) {
for (Observable oo : observables.remove(o)) {
oo.removeListener(resortListenerWeak);
}

fire(new SetChangeListener.Change<E>(this) {
@Override
public boolean wasAdded() {
return false;
}

@Override
public boolean wasRemoved() {
return true;
}

@Override
public E getElementAdded() {
return null;
}

@Override
public E getElementRemoved() {
return o;
}
});
}

private void fire(SetChangeListener.Change<E> evt) {
for (SetChangeListener<? super E> oo : observers) {
oo.onChanged(evt);
}

for (InvalidationListener oo : invalidationListeners) {
oo.invalidated(this);
}
}

@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
private final Iterator<E> it = backing.iterator();
private E previous;

@Override
public void forEachRemaining(Consumer<? super E> action) {
previous = null;
it.forEachRemaining(action);
}

@Override
public boolean hasNext() {
return it.hasNext();
}

@Override
public E next() {
previous = it.next();
return previous;
}

@Override
public void remove() {
it.remove();
onRemoved(previous);
}
};
}

@Override
public void forEach(Consumer<? super E> action) {
backing.forEach(action);
}

@Override
public boolean isEmpty() {
return backing.isEmpty();
}

@Override
public Stream<E> parallelStream() {
return backing.parallelStream();
}

@Override
public Stream<E> stream() {
return backing.stream();
}

@Override
public void addListener(InvalidationListener listener) {
invalidationListeners.add(listener);
}

@Override
public void removeListener(InvalidationListener listener) {
invalidationListeners.remove(listener);
}

@Override
public Object[] toArray() {
return backing.toArray();
}

@Override
public <T> T[] toArray(T[] a) {
return backing.toArray(a);
}

@Override
public Spliterator<E> spliterator() {
return backing.spliterator();
}

@Override
public int size() {
return backing.size();
}

@SuppressWarnings("unchecked")
@Override
public void clear() {
Object[] os = backing.toArray();
backing.clear();

for (Object o : os) {
onRemoved((E) o);
}
}

@Override
public void addListener(SetChangeListener<? super E> listener) {
observers.add(listener);
}

@Override
public void removeListener(SetChangeListener<? super E> listener) {
observers.remove(listener);
}

@Override
public Comparator<? super E> comparator() {
return comparator;
}

@Override
public SortedSet<E> subSet(E fromElement, E toElement) {
if (Objects.equals(fromElement, toElement)) {
return emptySortedSet();
} else {
return Sets.filter(this, e -> (comparator.compare(e, fromElement) >= 0) && (comparator.compare(e, toElement) < 0));
}
}

@Override
public SortedSet<E> headSet(E toElement) {
return Sets.filter(this, e -> comparator.compare(e, toElement) < 0);
}

@Override
public SortedSet<E> tailSet(E fromElement) {
return Sets.filter(this, e -> comparator.compare(e, fromElement) >= 0);
}

@Override
public E first() {
if (backing.isEmpty()) {
throw new NoSuchElementException();
} else {
return backing.get(0);
}
}

@Override
public E last() {
if (backing.isEmpty()) {
throw new NoSuchElementException();
} else {
return backing.get(backing.size() - 1);
}
}

@Override
public boolean add(E e) {
int pos = binarySearch(backing, e, comparator);

if (pos >= 0) {
return false;
} else {
backing.add(-pos - 1, e);
onAdded(e);
return true;
}
}

@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
int pos = binarySearch(backing, (E) o, comparator);
return (pos >= 0) && backing.get(pos).equals(o);
}

@SuppressWarnings("unchecked")
@Override
public boolean remove(Object o) {
int pos = binarySearch(backing, (E) o, comparator);

if ((pos >= 0) && backing.get(pos).equals(o)) {
backing.remove(pos);
onRemoved((E) o);
return true;
} else {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.reactfx.collection;

import java.util.SortedSet;

import javafx.collections.ObservableSet;

/**
* A {@link SortedSet} that is also {@linkplain ObservableSet observable}.
* Implementations of this interface provide a read-only {@link #listView} of
* their contents, which is also sorted.
*
* @see ObservableSortedArraySet
*/
public interface ObservableSortedSet<E> extends ObservableSet<E>, SortedSet<E> {
/**
* A read-only {@link LiveList} view of this
* {@link ObservableSortedSet}'s contents. It will issue events whenever
* items are added to or removed from this {@code ObservableSortedSet},
* and when their sort order changes.
*/
LiveList<E> listView();
}
Loading