001package jmri.implementation.decorators;
002
003import jmri.*;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007import java.beans.PropertyChangeEvent;
008import java.beans.PropertyChangeListener;
009import java.util.concurrent.atomic.AtomicInteger;
010
011import jmri.util.ThreadingUtil;
012
013/**
014 * Timeout decorator implementation for reporters.
015 * <p>
016 * This decorator causes the current report to be reset to nullified after a
017 * preset time period.  This is to be used for reporter hardware that reports
018 * a value, but never reports the value is cleared (e.g. most RFID readers).
019 * <hr>
020 * This file is part of JMRI.
021 * <p>
022 * based on TimeoutRfidReporter originally implemented by Matthew Harris
023 * <p>
024 * JMRI is free software; you can redistribute it and/or modify it under the
025 * terms of version 2 of the GNU General Public License as published by the Free
026 * Software Foundation. See the "COPYING" file for a copy of this license.
027 * <p>
028 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
029 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
030 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
031 * Based
032 * @author Matthew Harris Copyright (C) 2014
033 * @author Paul Bender Copyright (C) 2020
034 * @since 4.19.4
035 */
036public class TimeoutReporter extends AbstractNamedBeanDecorator implements Reporter, IdTagListener,PropertyChangeListener {
037
038    /**
039     * The reporter this object is a decorator for
040     */
041    private Reporter reporter;
042
043
044    /**
045     * Timeout in ms
046     */
047    private static final int TIMEOUT = 2000;
048
049    /**
050     * Timeout in ms used by this object.
051     */
052    private int timeout = TIMEOUT;
053
054    /**
055     * Counts the reports that started a timeout; only the timeout of the last
056     * report can clear the report.
057     */
058    private final AtomicInteger timeoutGeneration = new AtomicInteger();
059
060    private volatile boolean disposed = false;
061
062    public TimeoutReporter(Reporter reporter) {
063        super(reporter);
064        this.reporter = reporter;
065        this.reporter.addPropertyChangeListener(this);
066    }
067
068    @Override
069    public Object getLastReport() {
070        return reporter.getLastReport();
071    }
072
073    @Override
074    public Object getCurrentReport() {
075        return reporter.getCurrentReport();
076    }
077
078    @Override
079    public void setReport(Object r) {
080        reporter.setReport(r);
081    }
082
083    @Override
084    public int getState() {
085        return reporter.getState();
086    }
087
088    @Override
089    public void dispose() {
090        disposed = true;
091        super.dispose();
092        reporter.dispose();
093    }
094
095    @Override
096    public void setState(int i) throws JmriException {
097        reporter.setState(i);
098    }
099
100    @Override
101    public void notify(IdTag r) {
102        if(reporter instanceof IdTagListener ){
103            ((IdTagListener)reporter).notify(r);
104        }
105        if (r != null) {
106            // also when the same tag is seen again, which doesn't change the current report
107            restartTimeout();
108        }
109    }
110
111    @Override
112    public void notifySeenElsewhere(IdTag tag, Reporter newReporter) {
113        reporter.notifySeenElsewhere(tag, newReporter);
114    }
115
116    /**
117     * {@inheritDoc}
118     *
119     *  Intercepts property change events from the underlying reporter
120     *  and forwards them to property change listeners for this reporter.
121     */
122    @Override
123    public void propertyChange(PropertyChangeEvent evt) {
124        log.debug("event received {}",evt);
125         if(evt.getPropertyName().equals("currentReport") &&
126            evt.getNewValue()!=null ){
127                 restartTimeout();
128         }
129         // fire off the property change for listeners of this TimeoutReporter.
130         firePropertyChange(evt.getPropertyName(),evt.getOldValue(),evt.getNewValue());
131    }
132
133    /**
134     * Start the timeout again from now. When it ends without a later report,
135     * the report is cleared on the layout thread.
136     */
137    private void restartTimeout() {
138        final int generation = timeoutGeneration.incrementAndGet();
139        ThreadingUtil.runOnLayoutDelayed(() -> {
140            if (!disposed && generation == timeoutGeneration.get()) {
141                log.debug("Timeout-{}", getSystemName());
142                reporter.setReport(null);
143            }
144        }, timeout);
145    }
146
147    /**
148     * Set the timeout, for tests.
149     *
150     * @param ms the timeout in milliseconds
151     */
152    void setTimeout(int ms) {
153        timeout = ms;
154    }
155
156    private static final Logger log = LoggerFactory.getLogger(TimeoutReporter.class);
157
158}