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}