001package jmri.util;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.Collection;
008import java.util.concurrent.ArrayBlockingQueue;
009import java.util.concurrent.BlockingQueue;
010import java.util.concurrent.TimeUnit;
011import javax.annotation.Nonnull;
012import javax.annotation.concurrent.ThreadSafe;
013import jmri.NamedBean;
014
015
016/**
017 * Gathers PropertyChangeEvents that might occur in overlapping threads and at
018 * overlapping times, presenting them as requested.
019 * <p>
020 * Listeners are installed when the object is constructed. {@link #dispose()}
021 * detaches those listeners, after which the object should not be used. It is
022 * not an error to call {@link #dispose()} multiple times.
023 * <p>
024 * Although this could be more generic than NamedBean, there's no single
025 * interface that specifies "can call addPropertyChangeListener(..)".
026 *
027 * @author Bob Jacobsen Copyright 2017
028 */
029@ThreadSafe
030public class PropertyChangeEventQueue {
031
032    /**
033     * @param collection Set of NamedBeans whose events should be handled. Keeps
034     *                   a copy of the contents, so future changes irrelevant.
035     */
036    public PropertyChangeEventQueue(@Nonnull Collection<NamedBean> collection) {
037        this();
038        for (NamedBean item : collection) {
039            items.add(item);
040            item.addPropertyChangeListener(listener);
041        }
042        if (log.isTraceEnabled()) {
043            log.trace("Created {}", this.toString());
044        }
045    }
046
047    /**
048     * @param array Set of NamedBeans whose events should be handled Keeps a
049     *              copy of the contents, so future changes irrelevant.
050     */
051    public PropertyChangeEventQueue(@Nonnull NamedBean[] array) {
052        this(Arrays.asList(array));
053    }
054
055    // Empty object makes no sense
056    private PropertyChangeEventQueue() {
057    }
058
059    final Collection<NamedBean> items = new ArrayList<>();
060    final static int MAX_SIZE = 100;
061    final BlockingQueue<PropertyChangeEvent> dq = new ArrayBlockingQueue<>(MAX_SIZE);
062    final PropertyChangeListener listener = (PropertyChangeEvent e) -> {
063        log.trace(" handling event {}", e);
064        boolean success = dq.offer(e);
065        if (!success) {
066            log.error("Could not process event {} from {} in {}", e.getPropertyName(), e.getSource(), dq);
067        }
068    };
069
070    /**
071     * Dispose by dropping the listeners to all the specified
072     * {@link NamedBean}s. The object should not be used again after calling
073     * this. It is not an error to call this multiple times.
074     */
075    public void dispose() {
076        log.trace("dispose() {}", items);
077        items.forEach((bean) -> {
078            bean.removePropertyChangeListener(listener);
079        });
080    }
081
082    public PropertyChangeEvent take() throws InterruptedException {
083        return dq.take();
084    }
085
086    public PropertyChangeEvent poll(long timeout, TimeUnit unit) throws InterruptedException {
087        return dq.poll(timeout, unit);
088    }
089
090    @Override
091    public String toString() {
092        StringBuffer b = new StringBuffer("PropertyChangeEventQueue for");
093        items.stream().forEachOrdered((bean) -> {
094            b.append(" (\"");
095            b.append(bean.getDisplayName());
096            b.append("\")");
097        });
098        return new String(b);
099    }
100
101    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PropertyChangeEventQueue.class);
102}