001package jmri.jmrit.logixng.implementation; 002 003import java.util.*; 004 005import javax.annotation.Nonnull; 006 007import jmri.*; 008import jmri.jmrit.logixng.*; 009import jmri.jmrit.logixng.Module; 010import jmri.jmrit.logixng.Stack; 011import jmri.jmrit.logixng.util.LogixNG_Thread; 012import jmri.util.*; 013 014/** 015 * The default implementation of ConditionalNG. 016 * 017 * @author Daniel Bergqvist Copyright 2019 018 */ 019public class DefaultConditionalNG extends AbstractBase 020 implements ConditionalNG, FemaleSocketListener { 021 022 private final LogixNG_Thread _thread; 023 private int _startupThreadId; 024 private Base _parent = null; 025 private String _socketSystemName = null; 026 private final FemaleDigitalActionSocket _femaleSocket; 027 private boolean _enabled = true; 028 private boolean _executeAtStartup = true; 029 private final ExecuteLock _executeLock = new ExecuteLock(); 030 private boolean _runDelayed = true; 031 private final Stack _stack = new DefaultStack(); 032 private SymbolTable _symbolTable; 033 034 035 public DefaultConditionalNG(String sys, String user) 036 throws BadUserNameException, BadSystemNameException { 037 this(sys, user, LogixNG_Thread.DEFAULT_LOGIXNG_THREAD); 038 } 039 040 public DefaultConditionalNG(String sys, String user, int threadID) 041 throws BadUserNameException, BadSystemNameException { 042 super(sys, user); 043 044 _startupThreadId = threadID; 045 _thread = LogixNG_Thread.getThread(threadID); 046 _thread.setThreadInUse(); 047 048 // Do this test here to ensure all the tests are using correct system names 049 Manager.NameValidity isNameValid = InstanceManager.getDefault(ConditionalNG_Manager.class).validSystemNameFormat(mSystemName); 050 if (isNameValid != Manager.NameValidity.VALID) { 051 throw new IllegalArgumentException("system name is not valid"); 052 } 053 _femaleSocket = new DefaultFemaleDigitalActionSocket(this, this, "A"); 054 } 055 056 /** {@inheritDoc} */ 057 @Override 058 public LogixNG_Thread getCurrentThread() { 059 return _thread; 060 } 061 062 /** {@inheritDoc} */ 063 @Override 064 public int getStartupThreadId() { 065 return _startupThreadId; 066 } 067 068 /** {@inheritDoc} */ 069 @Override 070 public void setStartupThreadId(int threadId) { 071 int oldStartupThreadId = _startupThreadId; 072 _startupThreadId = threadId; 073 firePropertyChange("Thread", oldStartupThreadId, _startupThreadId); 074 } 075 076 /** {@inheritDoc} */ 077 @Override 078 public Base getParent() { 079 return _parent; 080 } 081 082 /** {@inheritDoc} */ 083 @Override 084 public void setParent(Base parent) { 085 _parent = parent; 086 087 if (isActive()) registerListeners(); 088 else unregisterListeners(); 089 } 090 091 /** {@inheritDoc} */ 092 @Override 093 public FemaleDigitalActionSocket getFemaleSocket() { 094 return _femaleSocket; 095 } 096 097 /** {@inheritDoc} */ 098 @Override 099 public void setRunDelayed(boolean value) { 100 _runDelayed = value; 101 } 102 103 /** {@inheritDoc} */ 104 @Override 105 public boolean getRunDelayed() { 106 return _runDelayed; 107 } 108 109 private void runOnLogixNG_Thread( 110 @Nonnull ThreadingUtil.ThreadAction ta, 111 boolean allowRunDelayed) { 112 113 if (_runDelayed && allowRunDelayed) { 114 _thread.runOnLogixNGEventually(ta); 115 } else { 116 _thread.runOnLogixNG(ta); 117 } 118 } 119 120 /** {@inheritDoc} */ 121 @Override 122 public void execute() { 123 if (_executeAtStartup || !getLogixNG().isStartup()) { 124 if (_executeLock.once()) { 125 runOnLogixNG_Thread(new ExecuteTask(this, _executeLock, null), true); 126 } 127 } 128 } 129 130 /** {@inheritDoc} */ 131 @Override 132 public void execute(boolean allowRunDelayed) { 133 if (_executeLock.once()) { 134 runOnLogixNG_Thread(new ExecuteTask(this, _executeLock, null), allowRunDelayed); 135 } 136 } 137 138 /** {@inheritDoc} */ 139 @Override 140 public void execute(FemaleDigitalActionSocket socket) { 141 runOnLogixNG_Thread(() -> {internalExecute(this, socket);}, true); 142 } 143 144 /** 145 * Executes a LogixNG Module. 146 * @param module The module to be executed 147 * @param parameters The parameters 148 */ 149 public static void executeModule(Module module, Map<String, Object> parameters) 150 throws IllegalArgumentException { 151 152 if (module == null) { 153 throw new IllegalArgumentException("The parameter \"module\" is null"); 154 } 155 if (!(module.getRootSocket() instanceof DefaultFemaleDigitalActionSocket)) { 156 throw new IllegalArgumentException("The module " + module.getDisplayName() + " is not a DigitalActionModule"); 157 } 158 if (parameters == null) { 159 throw new IllegalArgumentException("The parameter \"parameters\" is null"); 160 } 161 162 LogixNG_Thread thread = LogixNG_Thread.getThread(LogixNG_Thread.DEFAULT_LOGIXNG_THREAD); 163 ConditionalNG conditionalNG = new DefaultConditionalNG("IQC0000000", null); 164 InternalFemaleSocket socket = new InternalFemaleSocket(conditionalNG, module, parameters); 165 thread.runOnLogixNGEventually(() -> { internalExecute(conditionalNG, socket); }); 166 } 167 168 private static class InternalFemaleSocket extends DefaultFemaleDigitalActionSocket { 169 170 private final ConditionalNG _conditionalNG; 171 private final Module _module; 172 private final Map<String, Object> _parameters; 173 174 public InternalFemaleSocket(ConditionalNG conditionalNG, Module module, Map<String, Object> parameters) { 175 super(null, new FemaleSocketListener(){ 176 @Override 177 public void connected(FemaleSocket socket) { 178 // Do nothing 179 } 180 181 @Override 182 public void disconnected(FemaleSocket socket) { 183 // Do nothing 184 } 185 }, "A"); 186 _conditionalNG = conditionalNG; 187 _module = module; 188 _parameters = parameters; 189 } 190 191 @Override 192 public void execute() throws JmriException { 193 FemaleSocket socket = _module.getRootSocket(); 194 if (!(socket instanceof DefaultFemaleDigitalActionSocket)) { 195 throw new IllegalArgumentException("The module " + _module.getDisplayName() + " is not a DigitalActionModule"); 196 } 197 198 synchronized(this) { 199 SymbolTable oldSymbolTable = _conditionalNG.getSymbolTable(); 200 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(_conditionalNG); 201 List<Module.ParameterData> _parameterData = new ArrayList<>(); 202 for (Module.Parameter p : _module.getParameters()) { 203 _parameterData.add(new Module.ParameterData( 204 p.getName(), SymbolTable.InitialValueType.None, "", 205 Module.ReturnValueType.None, "")); 206 } 207 newSymbolTable.createSymbols(_conditionalNG.getSymbolTable(), _parameterData); 208 for (var entry : _parameters.entrySet()) { 209 newSymbolTable.setValue(entry.getKey(), entry.getValue()); 210 } 211 _conditionalNG.setSymbolTable(newSymbolTable); 212 213 ((DefaultFemaleDigitalActionSocket)socket).execute(); 214 _conditionalNG.setSymbolTable(oldSymbolTable); 215 } 216 } 217 } 218 219 private static void internalExecute(ConditionalNG conditionalNG, FemaleDigitalActionSocket femaleSocket) { 220 if (conditionalNG.isEnabled()) { 221 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG); 222 223 try { 224 conditionalNG.setCurrentConditionalNG(conditionalNG); 225 226 conditionalNG.setSymbolTable(newSymbolTable); 227 228 LogixNG logixNG = conditionalNG.getLogixNG(); 229 InlineLogixNG inlineLogixNG = null; 230 if (logixNG != null) { 231 inlineLogixNG = logixNG.getInlineLogixNG(); 232 } 233 if (inlineLogixNG != null) { 234 List<SymbolTable.VariableData> localVariables = new ArrayList<>(); 235 localVariables.add(new SymbolTable.VariableData( 236 "__InlineLogixNG__", SymbolTable.InitialValueType.String, 237 inlineLogixNG.getNameString())); 238// localVariables.add(new SymbolTable.VariableData( 239// "__PositionableId__", SymbolTable.InitialValueType.String, 240// inlineLogixNG.getId())); 241 localVariables.add(new SymbolTable.VariableData( 242 "__Editor__", SymbolTable.InitialValueType.String, 243 inlineLogixNG.getEditorName())); 244 newSymbolTable.createSymbols(localVariables); 245 } 246 247 if (femaleSocket != null) { 248 femaleSocket.execute(); 249 } else { 250 conditionalNG.getFemaleSocket().execute(); 251 } 252 } catch (ReturnException | ExitException e) { 253 // A Return action in a ConditionalNG causes a ReturnException so this is okay. 254 // An Exit action in a ConditionalNG causes a ExitException so this is okay. 255 } catch (PassThruException e) { 256 // This happens due to a a Break action or a Continue action that isn't handled. 257 log.info("ConditionalNG {} was aborted during execute: {}", 258 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 259 } catch (AbortConditionalNGExecutionException e) { 260 if (InstanceManager.getDefault(LogixNGPreferences.class).getShowSystemNameInException()) { 261 log.warn("ConditionalNG {} was aborted during execute in the item {}: {}", 262 conditionalNG.getSystemName(), e.getMaleSocket().getSystemName(), e.getCause(), e.getCause()); 263 } else { 264 log.warn("ConditionalNG {} was aborted during execute: {}", 265 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 266 } 267 } catch (JmriException | RuntimeException e) { 268// LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}", 269// conditionalNG.getSystemName(), e, e); 270 log.warn("ConditionalNG {} got an exception during execute: {}", 271 conditionalNG.getSystemName(), e, e); 272 } 273 274 conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable()); 275 } 276 } 277 278 private static class ExecuteTask implements ThreadingUtil.ThreadAction { 279 280 private final ConditionalNG _conditionalNG; 281 private final ExecuteLock _executeLock; 282 private final FemaleDigitalActionSocket _localFemaleSocket; 283 284 public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) { 285 _conditionalNG = conditionalNG; 286 _executeLock = executeLock; 287 _localFemaleSocket = femaleSocket; 288 } 289 290 @Override 291 public void run() { 292 while (_executeLock.loop()) { 293 internalExecute(_conditionalNG, _localFemaleSocket); 294 } 295 } 296 297 } 298 299 /** 300 * Set the current ConditionalNG. 301 * @param conditionalNG the current ConditionalNG 302 */ 303 @Override 304 public void setCurrentConditionalNG(ConditionalNG conditionalNG) { 305 if (this != conditionalNG) { 306 throw new UnsupportedOperationException("The new conditionalNG must be the same as myself"); 307 } 308 for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) { 309 m.setCurrentConditionalNG(conditionalNG); 310 } 311 } 312 313 /** {@inheritDoc} */ 314 @Override 315 public Stack getStack() { 316 return _stack; 317 } 318 319 /** {@inheritDoc} */ 320 @Override 321 public SymbolTable getSymbolTable() { 322 return _symbolTable; 323 } 324 325 /** {@inheritDoc} */ 326 @Override 327 public void setSymbolTable(SymbolTable symbolTable) { 328 _symbolTable = symbolTable; 329 } 330 331 @Override 332 public String getBeanType() { 333 return Bundle.getMessage("BeanNameConditionalNG"); 334 } 335 336 @Override 337 public void setState(int s) throws JmriException { 338 log.warn("Unexpected call to setState in DefaultConditionalNG."); // NOI18N 339 } 340 341 @Override 342 public int getState() { 343 log.warn("Unexpected call to getState in DefaultConditionalNG."); // NOI18N 344 return UNKNOWN; 345 } 346 347 @Override 348 public void connected(FemaleSocket socket) { 349 _socketSystemName = socket.getConnectedSocket().getSystemName(); 350 } 351 352 @Override 353 public void disconnected(FemaleSocket socket) { 354 _socketSystemName = null; 355 } 356 357 @Override 358 public String getShortDescription(Locale locale) { 359 return "ConditionalNG: "+getDisplayName(); 360 } 361 362 @Override 363 public String getLongDescription(Locale locale) { 364 if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) { 365 return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName(); 366 } 367 return "ConditionalNG: "+getDisplayName(); 368 } 369 370 @Override 371 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 372 if (index != 0) { 373 throw new IllegalArgumentException( 374 String.format("index has invalid value: %d", index)); 375 } 376 377 return _femaleSocket; 378 } 379 380 @Override 381 public int getChildCount() { 382 return 1; 383 } 384 385 @Override 386 public Category getCategory() { 387 throw new UnsupportedOperationException("Not supported."); 388 } 389 390 @Override 391 public void setSocketSystemName(String systemName) { 392 if ((systemName == null) || (!systemName.equals(_socketSystemName))) { 393 _femaleSocket.disconnect(); 394 } 395 _socketSystemName = systemName; 396 } 397 398 @Override 399 public String getSocketSystemName() { 400 return _socketSystemName; 401 } 402 403 /** {@inheritDoc} */ 404 @Override 405 final public void setup() { 406 if (!_femaleSocket.isConnected() 407 || !_femaleSocket.getConnectedSocket().getSystemName() 408 .equals(_socketSystemName)) { 409 410 _femaleSocket.disconnect(); 411 412 if (_socketSystemName != null) { 413 try { 414 MaleSocket maleSocket = 415 InstanceManager.getDefault(DigitalActionManager.class) 416 .getBySystemName(_socketSystemName); 417 if (maleSocket != null) { 418 _femaleSocket.connect(maleSocket); 419 maleSocket.setup(); 420 } else { 421 log.error("digital action is not found: {}", _socketSystemName); 422 } 423 } catch (SocketAlreadyConnectedException ex) { 424 // This shouldn't happen and is a runtime error if it does. 425 throw new RuntimeException("socket is already connected"); 426 } 427 } 428 } else { 429 _femaleSocket.setup(); 430 } 431 } 432 433 /** {@inheritDoc} */ 434 @Override 435 final public void disposeMe() { 436 _femaleSocket.dispose(); 437 } 438 439 /** {@inheritDoc} */ 440 @Override 441 public void setEnabled(boolean enable) { 442 _enabled = enable; 443 if (isActive()) { 444 LogixNG logixNG = getLogixNG(); 445 if ((logixNG != null) && logixNG.isActive()) { 446 registerListeners(); 447 if (_executeAtStartup) { 448 execute(); 449 } 450 } 451 } else { 452 unregisterListeners(); 453 } 454 } 455 456 /** {@inheritDoc} */ 457 @Override 458 public boolean isEnabled() { 459 return _enabled; 460 } 461 462 /** {@inheritDoc} */ 463 @Override 464 public void setExecuteAtStartup(boolean value) { 465 _executeAtStartup = value; 466 } 467 468 /** {@inheritDoc} */ 469 @Override 470 public boolean isExecuteAtStartup() { 471 return _executeAtStartup; 472 } 473 474 /** {@inheritDoc} */ 475 @Override 476 public synchronized boolean isListenersRegistered() { 477 return _listenersAreRegistered; 478 } 479 480 /** {@inheritDoc} */ 481 @Override 482 public synchronized void registerListenersForThisClass() { 483 _listenersAreRegistered = true; 484 } 485 486 /** {@inheritDoc} */ 487 @Override 488 public synchronized void unregisterListenersForThisClass() { 489 _listenersAreRegistered = false; 490 } 491 492 @Override 493 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) { 494 throw new UnsupportedOperationException("Not supported yet."); 495 } 496 497 @Override 498 public boolean existsInTree() { 499 return true; 500 } 501 502 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class); 503 504}