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

org.fxmisc.richtext.util.SubscribeableContentsObsSet Maven / Gradle / Ivy

package org.fxmisc.richtext.util;

import javafx.beans.InvalidationListener;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import org.reactfx.Subscription;

import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Function;

/**
 * An {@link javafx.collections.ObservableSet} implementation that allows one to subscribe to updates within
 * the elements themselves. For example, if one stored {@link javafx.scene.text.Text} in this set and one wanted
 * to be notified each time one of the text's {@link javafx.scene.text.Text#textProperty() textProperty()} changed,
 * one could use {@link #addSubscriber(Function)} with the function
 * 

 * text -> {
 *     EventStream<String> textValues = EventStreams.nonNullValuesOf(text.textProperty());
 *     return EventStreams.combine(textValues, otherTextValuesFromSomewhereElse)
 *         .subscribe(tuple2 -> {
 *              String quantity = tuple2.get1();
 *              String unit = tuple2.get2();
 *              someOtherObjectOnTheScreen.setText("Will send " + quantity + " " + unit + " to the department");
 *         });
 * }
 * 
* * When the element is removed from the set, the function's returned {@link Subscription} is * {@link Subscription#unsubscribe() unsubscribed} to prevent any memory leaks. * * @param the type of element in the set */ public class SubscribeableContentsObsSet extends AbstractSet implements ObservableSet { private final List> subscribers = new LinkedList<>(); private final List> changeListeners = new LinkedList<>(); private final List invalidationListeners = new LinkedList<>(); private final Map> map; public SubscribeableContentsObsSet() { this(null); } public SubscribeableContentsObsSet(Comparator comparator) { this.map = new TreeMap<>(comparator); } @Override public Iterator iterator() { return map.keySet().iterator(); } @Override public int size() { return map.size(); } @Override public boolean add(E e) { Objects.requireNonNull(e, "Cannot add a null object to this list"); if (map.containsKey(e)) { return false; } // optimize for our use cases; initial capacity may need to be changed in future versions List list = new ArrayList<>(1); subscribers.stream() .map(f -> f.apply(e)) .forEach(list::add); map.put(e, list); invalidateSet(); fireElementAdded(e); return true; } @SuppressWarnings("unchecked") @Override public boolean remove(Object o) { List list = map.remove(o); if (list == null) { return false; } else { list.forEach(Subscription::unsubscribe); invalidateSet(); fireElementRemoved((E) o); return true; } } /** * Subscribes to all current and future elements' internal changes in this set until either they are removed * or this subscriber is removed by calling {@link Subscription#unsubscribe() unsubscribe} on the function's * returned {@link Subscription}. */ public Subscription addSubscriber(Function subscriber) { Objects.requireNonNull(subscriber); subscribers.add(subscriber); List keys = new ArrayList<>(map.keySet()); keys.forEach(key -> { List otherSubs = map.get(key); Subscription sub = subscriber.apply(key); otherSubs.add(sub); map.put(key, otherSubs); }); return () -> removeSubscriber(subscriber); } /** * Helper method for adding a change listener that can be removed by calling * {@link Subscription#unsubscribe() unsubscribe} on the returned {@link Subscription}. */ public Subscription addChangeListener(SetChangeListener listener) { addListener(listener); return () -> removeListener(listener); } /** * Helper method for adding an invalidation listener that can be removed by calling * {@link Subscription#unsubscribe() unsubscribe} on the returned {@link Subscription}. */ public Subscription addInvalidationListener(InvalidationListener listener) { addListener(listener); return () -> removeListener(listener); } @Override public void addListener(SetChangeListener listener) { changeListeners.add(listener); } @Override public void removeListener(SetChangeListener listener) { changeListeners.remove(listener); } @Override public void addListener(InvalidationListener listener) { invalidationListeners.add(listener); } @Override public void removeListener(InvalidationListener listener) { invalidationListeners.remove(listener); } private void invalidateSet() { invalidationListeners.forEach(l -> l.invalidated(this)); } private void removeSubscriber(Function subscriber) { // remove the subscriber while calculating its index // which corresponds to the same index in the key's list. int index = -1; int i = 0; Iterator> iter = subscribers.iterator(); while (iter.hasNext() && index == -1) { Function s = iter.next(); if (s == subscriber) { iter.remove(); index = i; } else { i++; } } final int finalIndex = index; List keys = new ArrayList<>(map.keySet()); // if this subscriber is being removed, we no longer need to store // its corresponding subscription. Thus, it can be removed and unsubscribed keys.forEach(key -> map.get(key).remove(finalIndex).unsubscribe()); } private void fireElementAdded(E elem) { SetChangeListener.Change change = new SetChangeListener.Change(this) { @Override public boolean wasAdded() { return true; } @Override public boolean wasRemoved() { return false; } @Override public E getElementAdded() { return elem; } @Override public E getElementRemoved() { return null; } }; changeListeners.forEach(l -> l.onChanged(change)); } private void fireElementRemoved(E elem) { SetChangeListener.Change change = new SetChangeListener.Change(this) { @Override public boolean wasAdded() { return false; } @Override public boolean wasRemoved() { return true; } @Override public E getElementAdded() { return null; } @Override public E getElementRemoved() { return elem; } }; changeListeners.forEach(l -> l.onChanged(change)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy