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}