001package jmri.jmrit.logixng.expressions; 002 003import jmri.util.TimerUnit; 004 005import java.util.*; 006 007import javax.annotation.Nonnull; 008 009import jmri.InstanceManager; 010import jmri.JmriException; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.util.*; 013import jmri.jmrit.logixng.util.parser.*; 014import jmri.util.TimerUtil; 015import jmri.util.TypeConversionUtil; 016 017/** 018 * An expression that waits some time before returning True. 019 * 020 * This expression returns False until some time has elapsed. Then it returns 021 * True once. After that, it returns False again until some time has elapsed. 022 * 023 * @author Daniel Bergqvist Copyright 2023 024 */ 025public class Timer extends AbstractDigitalExpression { 026 027 private static class StateAndTimerTask{ 028 ProtectedTimerTask _timerTask; 029 State _currentState = State.IDLE; 030 } 031 032 private enum State { IDLE, RUNNING, COMPLETED } 033 034 private final Map<ConditionalNG, StateAndTimerTask> _stateAndTimerTask = new HashMap<>(); 035 private int _delay; 036 private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct; 037 private TimerUnit _unit = TimerUnit.MilliSeconds; 038 private String _stateReference = ""; 039 private String _stateLocalVariable = ""; 040 private String _stateFormula = ""; 041 private ExpressionNode _stateExpressionNode; 042 043 044 public Timer(String sys, String user) 045 throws BadUserNameException, BadSystemNameException { 046 super(sys, user); 047 } 048 049 @Override 050 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 051 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 052 String sysName = systemNames.get(getSystemName()); 053 String userName = userNames.get(getSystemName()); 054 if (sysName == null) sysName = manager.getAutoSystemName(); 055 Timer copy = new Timer(sysName, userName); 056 copy.setComment(getComment()); 057 copy.setDelayAddressing(_stateAddressing); 058 copy.setDelay(_delay); 059 copy.setDelayFormula(_stateFormula); 060 copy.setDelayLocalVariable(_stateLocalVariable); 061 copy.setDelayReference(_stateReference); 062 copy.setUnit(_unit); 063 return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames); 064 } 065 066 /** {@inheritDoc} */ 067 @Override 068 public LogixNG_Category getCategory() { 069 return LogixNG_Category.COMMON; 070 } 071 072 /** 073 * Get a new timer task. 074 * @param conditionalNG the ConditionalNG 075 * @param timerDelay the time the timer should wait 076 * @param timerStart the time when the timer was started 077 */ 078 private ProtectedTimerTask getNewTimerTask(ConditionalNG conditionalNG, long timerDelay, long timerStart) throws JmriException { 079 080 return new ProtectedTimerTask() { 081 @Override 082 public void execute() { 083 try { 084 synchronized(Timer.this) { 085 StateAndTimerTask stateAndTimerTask = _stateAndTimerTask.get(conditionalNG); 086 stateAndTimerTask._timerTask = null; 087 088 long currentTime = System.currentTimeMillis(); 089 long currentTimerTime = currentTime - timerStart; 090 if (currentTimerTime < timerDelay) { 091 scheduleTimer(conditionalNG, timerDelay, timerStart); 092 } else { 093 stateAndTimerTask._currentState = State.COMPLETED; 094 if (conditionalNG.isListenersRegistered()) { 095 conditionalNG.execute(); 096 } 097 } 098 } 099 } catch (RuntimeException | JmriException e) { 100 log.error("Exception thrown", e); 101 } 102 } 103 }; 104 } 105 106 private void scheduleTimer(ConditionalNG conditionalNG, long timerDelay, long timerStart) throws JmriException { 107 synchronized(Timer.this) { 108 StateAndTimerTask stateAndTimerTask = _stateAndTimerTask.get(conditionalNG); 109 if (stateAndTimerTask._timerTask != null) { 110 stateAndTimerTask._timerTask.stopTimer(); 111 } 112 long currentTime = System.currentTimeMillis(); 113 long currentTimerTime = currentTime - timerStart; 114 stateAndTimerTask._timerTask = getNewTimerTask(conditionalNG, timerDelay, timerStart); 115 TimerUtil.schedule(stateAndTimerTask._timerTask, timerDelay - currentTimerTime); 116 } 117 } 118 119 private long getNewDelay(ConditionalNG conditionalNG) throws JmriException { 120 121 switch (_stateAddressing) { 122 case Direct: 123 return _delay; 124 125 case Reference: 126 return TypeConversionUtil.convertToLong(ReferenceUtil.getReference( 127 conditionalNG.getSymbolTable(), _stateReference)); 128 129 case LocalVariable: 130 SymbolTable symbolTable = conditionalNG.getSymbolTable(); 131 return TypeConversionUtil 132 .convertToLong(symbolTable.getValue(_stateLocalVariable)); 133 134 case Formula: 135 return _stateExpressionNode != null 136 ? TypeConversionUtil.convertToLong( 137 _stateExpressionNode.calculate( 138 conditionalNG.getSymbolTable())) 139 : 0; 140 141 default: 142 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 143 } 144 } 145 146 /** {@inheritDoc} */ 147 @Override 148 public boolean evaluate() throws JmriException { 149 synchronized(this) { 150 ConditionalNG conditionalNG = getConditionalNG(); 151 StateAndTimerTask stateAndTimerTask = _stateAndTimerTask 152 .computeIfAbsent(conditionalNG, o -> new StateAndTimerTask()); 153 154 switch (stateAndTimerTask._currentState) { 155 case RUNNING: 156 return false; 157 case COMPLETED: 158 stateAndTimerTask._currentState = State.IDLE; 159 return true; 160 case IDLE: 161 stateAndTimerTask._currentState = State.RUNNING; 162 if (stateAndTimerTask._timerTask != null) { 163 stateAndTimerTask._timerTask.stopTimer(); 164 } 165 long timerStart = System.currentTimeMillis(); 166 long timerDelay = getNewDelay(conditionalNG) * _unit.getMultiply(); 167 scheduleTimer(conditionalNG, timerDelay, timerStart); 168 return false; 169 default: 170 throw new UnsupportedOperationException("currentState has invalid state: "+stateAndTimerTask._currentState.name()); 171 } 172 } 173 } 174 175 public void setDelayAddressing(NamedBeanAddressing addressing) throws ParserException { 176 _stateAddressing = addressing; 177 parseDelayFormula(); 178 } 179 180 public NamedBeanAddressing getDelayAddressing() { 181 return _stateAddressing; 182 } 183 184 /** 185 * Get the delay. 186 * @return the delay 187 */ 188 public int getDelay() { 189 return _delay; 190 } 191 192 /** 193 * Set the delay. 194 * @param delay the delay 195 */ 196 public void setDelay(int delay) { 197 _delay = delay; 198 } 199 200 public void setDelayReference(@Nonnull String reference) { 201 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 202 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 203 } 204 _stateReference = reference; 205 } 206 207 public String getDelayReference() { 208 return _stateReference; 209 } 210 211 public void setDelayLocalVariable(@Nonnull String localVariable) { 212 _stateLocalVariable = localVariable; 213 } 214 215 public String getDelayLocalVariable() { 216 return _stateLocalVariable; 217 } 218 219 public void setDelayFormula(@Nonnull String formula) throws ParserException { 220 _stateFormula = formula; 221 parseDelayFormula(); 222 } 223 224 public String getDelayFormula() { 225 return _stateFormula; 226 } 227 228 private void parseDelayFormula() throws ParserException { 229 if (_stateAddressing == NamedBeanAddressing.Formula) { 230 Map<String, Variable> variables = new HashMap<>(); 231 232 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 233 _stateExpressionNode = parser.parseExpression(_stateFormula); 234 } else { 235 _stateExpressionNode = null; 236 } 237 } 238 239 /** 240 * Get the unit 241 * @return the unit 242 */ 243 public TimerUnit getUnit() { 244 return _unit; 245 } 246 247 /** 248 * Set the unit 249 * @param unit the unit 250 */ 251 public void setUnit(TimerUnit unit) { 252 _unit = unit; 253 } 254 255 @Override 256 public String getShortDescription(Locale locale) { 257 return Bundle.getMessage(locale, "Timer_Short"); 258 } 259 260 @Override 261 public String getLongDescription(Locale locale) { 262 String delay; 263 264 switch (_stateAddressing) { 265 case Direct: 266 delay = Bundle.getMessage(locale, "Timer_DelayByDirect", _unit.getTimeWithUnit(_delay)); 267 break; 268 269 case Reference: 270 delay = Bundle.getMessage(locale, "Timer_DelayByReference", _stateReference, _unit.toString()); 271 break; 272 273 case LocalVariable: 274 delay = Bundle.getMessage(locale, "Timer_DelayByLocalVariable", _stateLocalVariable, _unit.toString()); 275 break; 276 277 case Formula: 278 delay = Bundle.getMessage(locale, "Timer_DelayByFormula", _stateFormula, _unit.toString()); 279 break; 280 281 default: 282 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 283 } 284 285 return Bundle.getMessage(locale, "Timer_Long", delay); 286 } 287 288 /** {@inheritDoc} */ 289 @Override 290 public void setup() { 291 // Do nothing 292 } 293 294 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Timer.class); 295 296}