Why are there no ObservableQueues in JavaFX?
Asked Answered
T

1

10

Why are there no ObservableQueue in JavaFX? If we look at the Java 9 documentation (just to see if there are any changes from 8) for FXCollections, we see static helper methods to create Observable sets, lists, and maps. There are also some methods to create Observable float and integer arrays. However, there is no way to create an ObservableQueue. The Queue interface in Java has many interesting implementations, including ArrayDeque, DelayQueue, ConcurrentLinkedQueue, PriorityQueue, etc. What is the logic behind the reason why there is no support for creating ObservableQueues in JavaFX.

Tendon answered 11/2, 2015 at 8:36 Comment(0)
C
9

As @TomasMikula comments in @eckig's (now-deleted) answer, there probably just is not enough demand for an ObservableQueue. If you have a solid use-case, you should consider submitting a feature request.

In the meantime, it's not too hard to create a quick-and-dirty ObservableQueue implementing Queue and adding "observability" by subclassing ObservableListBase and wrapping a Queue implementation. Subclassing ObservableListBase is the "quick" part, but also the "dirty" part because you expose List methods as well as Queue methods; since an arbitrary Queue doesn't have a get(int index) the only way to implement that (that I can see) is to iterate through up to index. Anything that uses get to iterate through the ObservableQueue, regarding it as a List, will run in O(n^2) time. With that caveat, the following should work pretty well:

import java.util.LinkedList;
import java.util.Queue;

import javafx.collections.ObservableListBase;


public class ObservableQueue<E> extends ObservableListBase<E> implements Queue<E> {

    private final Queue<E> queue ;


    /**
     * Creates an ObservableQueue backed by the supplied Queue. 
     * Note that manipulations of the underlying queue will not result
     * in notification to listeners.
     * 
     * @param queue
     */
    public ObservableQueue(Queue<E> queue) {
        this.queue = queue ;
    }

    /**
     * Creates an ObservableQueue backed by a LinkedList.
     */
    public ObservableQueue() {
        this(new LinkedList<>());
    }

    @Override
    public boolean offer(E e) {
        beginChange();
        boolean result = queue.offer(e);
        if (result) {
            nextAdd(queue.size()-1, queue.size());
        }
        endChange();
        return result ;
    }

    @Override
    public boolean add(E e) {
        beginChange() ;
        try {
            queue.add(e);
            nextAdd(queue.size()-1, queue.size());
            return true ;
        } finally {
            endChange();
        }
    }


    @Override
    public E remove() {
        beginChange();
        try {
            E e = queue.remove();
            nextRemove(0, e);
            return e;
        } finally {
            endChange();
        }
    }

    @Override
    public E poll() {
        beginChange();
        E e = queue.poll();
        if (e != null) {
            nextRemove(0, e);
        }
        endChange();
        return e ;
    }

    @Override
    public E element() {
        return queue.element();
    }

    @Override
    public E peek() {
        return queue.peek();
    }

    @Override
    public E get(int index) {
        Iterator<E> iterator = queue.iterator();
        for (int i = 0; i < index; i++) iterator.next();
        return iterator.next();
    }

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

}

You can register ListChangeListeners with this to be notified of modifications to the queue. (Note that if you want to support extractors and update notifications, you'd need to do quite a bit more work...).

import javafx.collections.ListChangeListener.Change;

public class ObservableQueueTest {
    public static void main(String[] args) {
        ObservableQueue<String> oq = new ObservableQueue<>();
        oq.addListener((Change<? extends String> change) -> {
            while (change.next()) {
                if (change.wasAdded()) {
                    System.out.println("Added: "+change.getAddedSubList());
                }
                if (change.wasRemoved()) {
                    System.out.println("Removed: "+change.getRemoved());
                }
                if (change.wasUpdated()) {
                    System.out.println("Updated: "+oq.subList(change.getFrom(), change.getTo()));
                }
                if (change.wasReplaced()) {
                    System.out.println("Replaced");
                }
            }
        });

        oq.offer("One");
        oq.offer("Two");
        oq.offer("Three");

        System.out.println("Peek: "+oq.peek());
        System.out.println("Remove...");
        System.out.println(oq.remove());

        System.out.println("Element:");
        System.out.println(oq.element());

        System.out.println("get(1): "+oq.get(1));

        System.out.println("Poll: ");
        System.out.println(oq.poll());

        System.out.println("Poll again:");
        System.out.println(oq.poll());

        System.out.println("Poll should return null:");
        System.out.println(oq.poll());

        System.out.println("Element should throw exception:");
        System.out.println(oq.element());
    }

}
Condyle answered 12/2, 2015 at 2:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.