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