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

org.reactfx.collection.LiveList Maven / Gradle / Ivy

The newest version!
package org.reactfx.collection;

import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;

import javafx.beans.InvalidationListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.IndexRange;

import org.reactfx.EventStream;
import org.reactfx.EventStreamBase;
import org.reactfx.Observable;
import org.reactfx.Subscription;
import org.reactfx.collection.LiveList.QuasiChangeObserver;
import org.reactfx.collection.LiveList.QuasiModificationObserver;
import org.reactfx.util.AccumulatorSize;
import org.reactfx.util.Experimental;
import org.reactfx.util.WrapperBase;
import org.reactfx.value.Val;

/**
 * Adds additional methods to {@link ObservableList}.
 *
 * @param  type of list elements
 */
public interface LiveList
extends ObservableList, Observable> {

    /* ************ *
     * Nested Types *
     * ************ */

    public interface Observer {
        AccumulatorSize sizeOf(ListModificationSequence mods);
        O headOf(ListModificationSequence mods);
         ListModificationSequence tailOf(ListModificationSequence mods);

        void onChange(O change);
    }

    @FunctionalInterface
    public interface QuasiChangeObserver
    extends Observer> {

        @Override
        default AccumulatorSize sizeOf(
                ListModificationSequence mods) {
            return AccumulatorSize.ONE;
        }

        @Override
        default QuasiListChange headOf(
                ListModificationSequence mods) {
            return mods.asListChange();
        }

        @Override
        default  ListModificationSequence tailOf(
                ListModificationSequence mods) {
            throw new NoSuchElementException();
        }
    }

    @FunctionalInterface
    public interface QuasiModificationObserver
    extends Observer> {

        @Override
        default AccumulatorSize sizeOf(
                ListModificationSequence mods) {
            return AccumulatorSize.fromInt(mods.getModificationCount());
        }

        @Override
        default QuasiListModification headOf(
                ListModificationSequence mods) {
            return mods.getModifications().get(0);
        }

        @Override
        default  ListModificationSequence tailOf(
                ListModificationSequence mods) {
            return mods.asListChangeAccumulator().drop(1);
        }
    }


    /* *************** *
     * Default Methods *
     * *************** */

    default void addQuasiChangeObserver(QuasiChangeObserver observer) {
        addObserver(observer);
    }

    default void removeQuasiChangeObserver(QuasiChangeObserver observer) {
        removeObserver(observer);
    }

    default void addQuasiModificationObserver(QuasiModificationObserver observer) {
        addObserver(observer);
    }

    default void removeQuasiModificationObserver(QuasiModificationObserver observer) {
        removeObserver(observer);
    }

    default void addChangeObserver(Consumer> observer) {
        addQuasiChangeObserver(new ChangeObserverWrapper<>(this, observer));
    }

    default void removeChangeObserver(Consumer> observer) {
        removeQuasiChangeObserver(new ChangeObserverWrapper<>(this, observer));
    }

    default void addModificationObserver(Consumer> observer) {
        addQuasiModificationObserver(new ModificationObserverWrapper<>(this, observer));
    }

    default void removeModificationObserver(Consumer> observer) {
        removeQuasiModificationObserver(new ModificationObserverWrapper<>(this, observer));
    }

    default Subscription observeQuasiChanges(QuasiChangeObserver observer) {
        addQuasiChangeObserver(observer);
        return () -> removeQuasiChangeObserver(observer);
    }

    default Subscription observeQuasiModifications(QuasiModificationObserver observer) {
        addQuasiModificationObserver(observer);
        return () -> removeQuasiModificationObserver(observer);
    }

    default Subscription observeChanges(Consumer> observer) {
        addChangeObserver(observer);
        return () -> removeChangeObserver(observer);
    }

    default Subscription observeModifications(Consumer> observer) {
        addModificationObserver(observer);
        return () -> removeModificationObserver(observer);
    }

    @Override
    default void addListener(ListChangeListener listener) {
        addQuasiChangeObserver(new ChangeListenerWrapper<>(this, listener));
    }

    @Override
    default void removeListener(ListChangeListener listener) {
        removeQuasiChangeObserver(new ChangeListenerWrapper<>(this, listener));
    }

    @Override
    default void addListener(InvalidationListener listener) {
        addQuasiChangeObserver(new InvalidationListenerWrapper<>(this, listener));
    }

    @Override
    default void removeListener(InvalidationListener listener) {
        removeQuasiChangeObserver(new InvalidationListenerWrapper<>(this, listener));
    }

    default Subscription pin() {
        return observeQuasiChanges(qc -> {});
    }

    default Val sizeProperty() {
        return sizeOf(this);
    }

    default  LiveList map(Function f) {
        return map(this, f);
    }

    default  LiveList mapDynamic(
            ObservableValue> f) {
        return mapDynamic(this, f);
    }

    default SuspendableList suspendable() {
        return suspendable(this);
    }

    default MemoizationList memoize() {
        return memoize(this);
    }

    default Val reduce(BinaryOperator reduction) {
        return reduce(this, reduction);
    }

    @Experimental
    default Val reduceRange(
            ObservableValue range, BinaryOperator reduction) {
        return reduceRange(this, range, reduction);
    }

    @Experimental
    default  Val collapse(Function, ? extends T> f) {
        return collapse(this, f);
    }

    @Experimental
    default  Val collapseDynamic(
            ObservableValue, ? extends T>> f) {
        return collapseDynamic(this, f);
    }

    default EventStream> quasiChanges() {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                return observeQuasiChanges(this::emit);
            }
        };
    }

    default EventStream> changes() {
        return quasiChanges().map(qc -> QuasiListChange.instantiate(qc, this));
    }

    default EventStream> quasiModifications() {
        return new EventStreamBase>() {
            @Override
            protected Subscription observeInputs() {
                return observeQuasiModifications(this::emit);
            }
        };
    }

    default EventStream> modifications() {
        return quasiModifications().map(qm -> QuasiListModification.instantiate(qm, this));
    }


    /* ************** *
     * Static Methods *
     * ************** */

    static  Subscription observeQuasiChanges(
            ObservableList list,
            QuasiChangeObserver observer) {
        if(list instanceof LiveList) {
            LiveList lst = (LiveList) list;
            return lst.observeQuasiChanges(observer);
        } else {
            ListChangeListener listener = ch -> {
                QuasiListChange change = QuasiListChange.from(ch);
                observer.onChange(change);
            };
            list.addListener(listener);
            return () -> list.removeListener(listener);
        }
    }

    static  Subscription observeChanges(
            ObservableList list,
            Consumer> observer) {

        return observeQuasiChanges(
                list, qc -> observer.accept(QuasiListChange.instantiate(qc, list)));
    }

    static  EventStream> quasiChangesOf(
            ObservableList list) {
        if(list instanceof LiveList) {
            LiveList lst = (LiveList) list;
            return lst.quasiChanges();
        } else {
            return new EventStreamBase>() {
                @Override
                protected Subscription observeInputs() {
                    return LiveList.observeQuasiChanges(list, this::emit);
                }
            };
        }
    }

    static  EventStream> changesOf(ObservableList list) {
        return quasiChangesOf(list).map(qc -> QuasiListChange.instantiate(qc, list));
    }

    static Val sizeOf(ObservableList list) {
        return Val.create(() -> list.size(), list);
    }

    static  LiveList map(
            ObservableList list,
            Function f) {
        return new MappedList<>(list, f);
    }

    static  LiveList mapDynamic(
            ObservableList list,
            ObservableValue> f) {
        return new DynamicallyMappedList<>(list, f);
    }

    static  SuspendableList suspendable(ObservableList list) {
        if(list instanceof SuspendableList) {
            return (SuspendableList) list;
        } else {
            return new SuspendableListWrapper<>(list);
        }
    }

    static  MemoizationList memoize(ObservableList list) {
        if(list instanceof MemoizationList) {
            return (MemoizationList) list;
        } else {
            return new MemoizationListImpl<>(list);
        }
    }

    static  Val reduce(
            ObservableList list, BinaryOperator reduction) {
        return new ListReduction<>(list, reduction);
    }

    @Experimental
    static  Val reduceRange(
            ObservableList list,
            ObservableValue range,
            BinaryOperator reduction) {
        return new ListRangeReduction<>(list, range, reduction);
    }

    @Experimental
    static  Val collapse(
            ObservableList list,
            Function, ? extends T> f) {
        return Val.create(() -> f.apply(Collections.unmodifiableList(list)), list);
    }

    @Experimental
    static  Val collapseDynamic(
            ObservableList list,
            ObservableValue, ? extends T>> f) {
        return Val.create(
                () -> f.getValue().apply(Collections.unmodifiableList(list)),
                list, f);
    }

    /**
     * Returns a {@linkplain LiveList} view of the given
     * {@linkplain ObservableValue} {@code obs}. The returned list will have
     * size 1 when the given observable value is not {@code null} and size 0
     * otherwise.
     */
    static  LiveList wrapVal(ObservableValue obs) {
        return new ValAsList<>(obs);
    }
}


class ChangeObserverWrapper
extends WrapperBase>>
implements QuasiChangeObserver {
    private final ObservableList list;

    ChangeObserverWrapper(
            ObservableList list,
            Consumer> delegate) {
        super(delegate);
        this.list = list;
    }

    @Override
    public void onChange(QuasiListChange change) {
        getWrappedValue().accept(QuasiListChange.instantiate(change, list));
    }
}

class ModificationObserverWrapper
extends WrapperBase>>
implements QuasiModificationObserver {
    private final ObservableList list;

    ModificationObserverWrapper(
            ObservableList list,
            Consumer> delegate) {
        super(delegate);
        this.list = list;
    }

    @Override
    public void onChange(QuasiListModification change) {
        getWrappedValue().accept(QuasiListModification.instantiate(change, list));
    }
}

class InvalidationListenerWrapper
extends WrapperBase
implements QuasiChangeObserver {
    private final ObservableList list;

    public InvalidationListenerWrapper(
            ObservableList list,
            InvalidationListener listener) {
        super(listener);
        this.list = list;
    }

    @Override
    public void onChange(QuasiListChange change) {
        getWrappedValue().invalidated(list);
    }
}

class ChangeListenerWrapper
extends WrapperBase>
implements QuasiChangeObserver {
    private final ObservableList list;

    public ChangeListenerWrapper(
            ObservableList list,
            ListChangeListener listener) {
        super(listener);
        this.list = list;
    }

    @Override
    public void onChange(QuasiListChange change) {

        List> modifications =
                change.getModifications();

        if(modifications.isEmpty()) {
            return;
        }

        getWrappedValue().onChanged(new ListChangeListener.Change(list) {

            private int current = -1;

            @Override
            public int getFrom() {
                return modifications.get(current).getFrom();
            }

            @Override
            protected int[] getPermutation() {
                return new int[0]; // not a permutation
            }

            /* Can change to List and remove unsafe cast when
             * https://javafx-jira.kenai.com/browse/RT-39683 is resolved. */
            @Override
            @SuppressWarnings("unchecked")
            public List getRemoved() {
                // cast is safe, because the list is unmodifiable
                return (List) modifications.get(current).getRemoved();
            }

            @Override
            public int getTo() {
                return modifications.get(current).getTo();
            }

            @Override
            public boolean next() {
                if(current + 1 < modifications.size()) {
                    ++current;
                    return true;
                } else {
                    return false;
                }
            }

            @Override
            public void reset() {
                current = -1;
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy