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