001package jmri.jmrit.logixng.actions; 002 003import jmri.util.TimerUnit; 004 005import java.beans.PropertyChangeEvent; 006import java.beans.PropertyChangeListener; 007import java.util.*; 008 009import jmri.InstanceManager; 010import jmri.JmriException; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.implementation.DefaultSymbolTable; 013import jmri.jmrit.logixng.util.*; 014import jmri.util.TimerUtil; 015 016/** 017 * Executes an action when the expression is True. 018 * 019 * @author Daniel Bergqvist Copyright 2021 020 */ 021public class Timeout extends AbstractDigitalAction 022 implements FemaleSocketListener, PropertyChangeListener { 023 024 private ProtectedTimerTask _timerTask; 025 private final LogixNG_SelectInteger _selectDelay = new LogixNG_SelectInteger(this, this); 026 private final LogixNG_SelectEnum<TimerUnit> _selectTimerUnit = 027 new LogixNG_SelectEnum<>(this, TimerUnit.values(), TimerUnit.MilliSeconds, this); 028 private String _expressionSocketSystemName; 029 private String _actionSocketSystemName; 030 private final FemaleDigitalExpressionSocket _expressionSocket; 031 private final FemaleDigitalActionSocket _actionSocket; 032 private final InternalFemaleSocket _internalSocket = new InternalFemaleSocket(); 033 034 // These variables are used internally in this action 035 private long _timerDelay = 0; // Timer delay in milliseconds 036 private long _timerStart = 0; // Timer start in milliseconds 037 038 public Timeout(String sys, String user) { 039 super(sys, user); 040 _expressionSocket = InstanceManager.getDefault(DigitalExpressionManager.class) 041 .createFemaleSocket(this, this, "E"); 042 _actionSocket = InstanceManager.getDefault(DigitalActionManager.class) 043 .createFemaleSocket(this, this, "A"); 044 } 045 046 @Override 047 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 048 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 049 String sysName = systemNames.get(getSystemName()); 050 String userName = userNames.get(getSystemName()); 051 if (sysName == null) sysName = manager.getAutoSystemName(); 052 Timeout copy = new Timeout(sysName, userName); 053 copy.setComment(getComment()); 054 _selectDelay.copy(copy._selectDelay); 055 _selectTimerUnit.copy(copy._selectTimerUnit); 056 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 057 } 058 059 /** {@inheritDoc} */ 060 @Override 061 public LogixNG_Category getCategory() { 062 return LogixNG_Category.OTHER; 063 } 064 065 /** 066 * Get a new timer task. 067 */ 068 private ProtectedTimerTask getNewTimerTask(ConditionalNG conditionalNG, SymbolTable symbolTable) throws JmriException { 069 070 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(symbolTable); 071 072 return new ProtectedTimerTask() { 073 @Override 074 public void execute() { 075 try { 076 synchronized(Timeout.this) { 077 _timerTask = null; 078 long currentTimerTime = System.currentTimeMillis() - _timerStart; 079 if (currentTimerTime < _timerDelay) { 080 scheduleTimer(conditionalNG, symbolTable, _timerDelay - currentTimerTime); 081 } else { 082 _internalSocket.conditionalNG = conditionalNG; 083 _internalSocket.newSymbolTable = newSymbolTable; 084 conditionalNG.execute(_internalSocket); 085 } 086 } 087 } catch (RuntimeException | JmriException e) { 088 log.error("Exception thrown", e); 089 } 090 } 091 }; 092 } 093 094 private void scheduleTimer(ConditionalNG conditionalNG, SymbolTable symbolTable, long delay) throws JmriException { 095 if (_timerTask != null) _timerTask.stopTimer(); 096 _timerTask = getNewTimerTask(conditionalNG, symbolTable); 097 TimerUtil.schedule(_timerTask, delay); 098 } 099 100 public LogixNG_SelectInteger getSelectDelay() { 101 return _selectDelay; 102 } 103 104 public LogixNG_SelectEnum<TimerUnit> getSelectTimerUnit() { 105 return _selectTimerUnit; 106 } 107 108 /** {@inheritDoc} */ 109 @Override 110 public void execute() throws JmriException { 111 boolean result = _expressionSocket.evaluate(); 112 113 synchronized(this) { 114 if (result) { 115 if (_timerTask != null) { 116 _timerTask.stopTimer(); 117 _timerTask = null; 118 } 119 return; 120 } 121 122 // Don't restart timer if it's still running 123 if (_timerTask != null) return; 124 125 _timerDelay = _selectDelay.evaluateValue(getConditionalNG()) 126 * _selectTimerUnit.evaluateEnum(getConditionalNG()).getMultiply(); 127 _timerStart = System.currentTimeMillis(); 128 ConditionalNG conditonalNG = getConditionalNG(); 129 scheduleTimer(conditonalNG, conditonalNG.getSymbolTable(), _timerDelay); 130 } 131 } 132 133 @Override 134 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 135 switch (index) { 136 case 0: 137 return _expressionSocket; 138 139 case 1: 140 return _actionSocket; 141 142 default: 143 throw new IllegalArgumentException( 144 String.format("index has invalid value: %d", index)); 145 } 146 } 147 148 @Override 149 public int getChildCount() { 150 return 2; 151 } 152 153 @Override 154 public void connected(FemaleSocket socket) { 155 if (socket == _expressionSocket) { 156 _expressionSocketSystemName = socket.getConnectedSocket().getSystemName(); 157 } else if (socket == _actionSocket) { 158 _actionSocketSystemName = socket.getConnectedSocket().getSystemName(); 159 } else { 160 throw new IllegalArgumentException("unkown socket"); 161 } 162 } 163 164 @Override 165 public void disconnected(FemaleSocket socket) { 166 if (socket == _expressionSocket) { 167 _expressionSocketSystemName = null; 168 } else if (socket == _actionSocket) { 169 _actionSocketSystemName = null; 170 } else { 171 throw new IllegalArgumentException("unkown socket"); 172 } 173 } 174 175 @Override 176 public String getShortDescription(Locale locale) { 177 return Bundle.getMessage(locale, "Timeout_Short"); 178 } 179 180 @Override 181 public String getLongDescription(Locale locale) { 182 String delay = _selectDelay.getDescription(locale); 183 184 if ((_selectDelay.isDirectAddressing()) 185 && (_selectTimerUnit.isDirectAddressing())) { 186 187 return Bundle.getMessage(locale, 188 "Timeout_Long", 189 _expressionSocket.getName(), 190 _actionSocket.getName(), 191 _selectTimerUnit.getEnum().getTimeWithUnit(_selectDelay.getValue())); 192 } 193 194 String timeUnit = _selectTimerUnit.getDescription(locale); 195 196 return Bundle.getMessage(locale, 197 "Timeout_Long_Indirect", 198 _expressionSocket.getName(), 199 _actionSocket.getName(), 200 delay, 201 timeUnit); 202 } 203 204 public FemaleDigitalExpressionSocket getExpressionSocket() { 205 return _expressionSocket; 206 } 207 208 public String getExpressionSocketSystemName() { 209 return _expressionSocketSystemName; 210 } 211 212 public void setExpressionSocketSystemName(String systemName) { 213 _expressionSocketSystemName = systemName; 214 } 215 216 public FemaleDigitalActionSocket getActionSocket() { 217 return _actionSocket; 218 } 219 220 public String getActionSocketSystemName() { 221 return _actionSocketSystemName; 222 } 223 224 public void setActionSocketSystemName(String systemName) { 225 _actionSocketSystemName = systemName; 226 } 227 228 /** {@inheritDoc} */ 229 @Override 230 public void setup() { 231 try { 232 if ( !_expressionSocket.isConnected() 233 || !_expressionSocket.getConnectedSocket().getSystemName() 234 .equals(_expressionSocketSystemName)) { 235 236 String socketSystemName = _expressionSocketSystemName; 237 _expressionSocket.disconnect(); 238 if (socketSystemName != null) { 239 MaleSocket maleSocket = 240 InstanceManager.getDefault(DigitalExpressionManager.class) 241 .getBySystemName(socketSystemName); 242 if (maleSocket != null) { 243 _expressionSocket.connect(maleSocket); 244 maleSocket.setup(); 245 } else { 246 log.error("cannot load digital expression {}", socketSystemName); 247 } 248 } 249 } else { 250 _expressionSocket.getConnectedSocket().setup(); 251 } 252 253 if ( !_actionSocket.isConnected() 254 || !_actionSocket.getConnectedSocket().getSystemName() 255 .equals(_actionSocketSystemName)) { 256 257 String socketSystemName = _actionSocketSystemName; 258 _actionSocket.disconnect(); 259 if (socketSystemName != null) { 260 MaleSocket maleSocket = 261 InstanceManager.getDefault(DigitalActionManager.class) 262 .getBySystemName(socketSystemName); 263 _actionSocket.disconnect(); 264 if (maleSocket != null) { 265 _actionSocket.connect(maleSocket); 266 maleSocket.setup(); 267 } else { 268 log.error("cannot load digital action {}", socketSystemName); 269 } 270 } 271 } else { 272 _actionSocket.getConnectedSocket().setup(); 273 } 274 } catch (SocketAlreadyConnectedException ex) { 275 // This shouldn't happen and is a runtime error if it does. 276 throw new RuntimeException("socket is already connected"); 277 } 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public void registerListenersForThisClass() { 283 _selectDelay.registerListeners(); 284 _selectTimerUnit.registerListeners(); 285 } 286 287 /** {@inheritDoc} */ 288 @Override 289 public void unregisterListenersForThisClass() { 290 _selectDelay.unregisterListeners(); 291 _selectTimerUnit.unregisterListeners(); 292 } 293 294 /** {@inheritDoc} */ 295 @Override 296 public void propertyChange(PropertyChangeEvent evt) { 297 getConditionalNG().execute(); 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public void disposeMe() { 303 if (_timerTask != null) { 304 _timerTask.stopTimer(); 305 _timerTask = null; 306 } 307 } 308 309 310 private class InternalFemaleSocket extends jmri.jmrit.logixng.implementation.DefaultFemaleDigitalActionSocket { 311 312 private ConditionalNG conditionalNG; 313 private SymbolTable newSymbolTable; 314 315 public InternalFemaleSocket() { 316 super(null, new FemaleSocketListener(){ 317 @Override 318 public void connected(FemaleSocket socket) { 319 // Do nothing 320 } 321 322 @Override 323 public void disconnected(FemaleSocket socket) { 324 // Do nothing 325 } 326 }, "A"); 327 } 328 329 @Override 330 public void execute() throws JmriException { 331 if (conditionalNG == null) { throw new NullPointerException("conditionalNG is null"); } 332 if (_actionSocket != null) { 333 SymbolTable oldSymbolTable = conditionalNG.getSymbolTable(); 334 conditionalNG.setSymbolTable(newSymbolTable); 335 _actionSocket.execute(); 336 conditionalNG.setSymbolTable(oldSymbolTable); 337 } 338 } 339 340 } 341 342 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Timeout.class); 343 344}