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 (AbortConditionalNG_IgnoreException | ReturnException | ExitException e) { 253 // A AbortConditionalNG_IgnoreException should be ignored. 254 // A Return action in a ConditionalNG causes a ReturnException so this is okay. 255 // An Exit action in a ConditionalNG causes a ExitException so this is okay. 256 } catch (PassThruException e) { 257 // This happens due to a a Break action or a Continue action that isn't handled. 258 log.info("ConditionalNG {} was aborted during execute: {}", 259 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 260 } catch (AbortConditionalNGExecutionException e) { 261 if (InstanceManager.getDefault(LogixNGPreferences.class).getShowSystemNameInException()) { 262 log.warn("ConditionalNG {} was aborted during execute in the item {}: {}", 263 conditionalNG.getSystemName(), e.getMaleSocket().getSystemName(), e.getCause(), e.getCause()); 264 } else { 265 log.warn("ConditionalNG {} was aborted during execute: {}", 266 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 267 } 268 } catch (JmriException | RuntimeException e) { 269// LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}", 270// conditionalNG.getSystemName(), e, e); 271 log.warn("ConditionalNG {} got an exception during execute: {}", 272 conditionalNG.getSystemName(), e, e); 273 } 274 275 conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable()); 276 } 277 } 278 279 private static class ExecuteTask implements ThreadingUtil.ThreadAction { 280 281 private final ConditionalNG _conditionalNG; 282 private final ExecuteLock _executeLock; 283 private final FemaleDigitalActionSocket _localFemaleSocket; 284 285 public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) { 286 _conditionalNG = conditionalNG; 287 _executeLock = executeLock; 288 _localFemaleSocket = femaleSocket; 289 } 290 291 @Override 292 public void run() { 293 while (_executeLock.loop()) { 294 internalExecute(_conditionalNG, _localFemaleSocket); 295 } 296 } 297 298 } 299 300 /** 301 * Set the current ConditionalNG. 302 * @param conditionalNG the current ConditionalNG 303 */ 304 @Override 305 public void setCurrentConditionalNG(ConditionalNG conditionalNG) { 306 if (this != conditionalNG) { 307 throw new UnsupportedOperationException("The new conditionalNG must be the same as myself"); 308 } 309 for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) { 310 m.setCurrentConditionalNG(conditionalNG); 311 } 312 } 313 314 /** {@inheritDoc} */ 315 @Override 316 public Stack getStack() { 317 return _stack; 318 } 319 320 /** {@inheritDoc} */ 321 @Override 322 public SymbolTable getSymbolTable() { 323 return _symbolTable; 324 } 325 326 /** {@inheritDoc} */ 327 @Override 328 public void setSymbolTable(SymbolTable symbolTable) { 329 _symbolTable = symbolTable; 330 } 331 332 @Override 333 public String getBeanType() { 334 return Bundle.getMessage("BeanNameConditionalNG"); 335 } 336 337 @Override 338 public void setState(int s) throws JmriException { 339 log.warn("Unexpected call to setState in DefaultConditionalNG."); // NOI18N 340 } 341 342 @Override 343 public int getState() { 344 log.warn("Unexpected call to getState in DefaultConditionalNG."); // NOI18N 345 return UNKNOWN; 346 } 347 348 @Override 349 public void connected(FemaleSocket socket) { 350 _socketSystemName = socket.getConnectedSocket().getSystemName(); 351 } 352 353 @Override 354 public void disconnected(FemaleSocket socket) { 355 _socketSystemName = null; 356 } 357 358 @Override 359 public String getShortDescription(Locale locale) { 360 return "ConditionalNG: "+getDisplayName(); 361 } 362 363 @Override 364 public String getLongDescription(Locale locale) { 365 if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) { 366 return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName(); 367 } 368 return "ConditionalNG: "+getDisplayName(); 369 } 370 371 @Override 372 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 373 if (index != 0) { 374 throw new IllegalArgumentException( 375 String.format("index has invalid value: %d", index)); 376 } 377 378 return _femaleSocket; 379 } 380 381 @Override 382 public int getChildCount() { 383 return 1; 384 } 385 386 @Override 387 public Category getCategory() { 388 throw new UnsupportedOperationException("Not supported."); 389 } 390 391 @Override 392 public void setSocketSystemName(String systemName) { 393 if ((systemName == null) || (!systemName.equals(_socketSystemName))) { 394 _femaleSocket.disconnect(); 395 } 396 _socketSystemName = systemName; 397 } 398 399 @Override 400 public String getSocketSystemName() { 401 return _socketSystemName; 402 } 403 404 /** {@inheritDoc} */ 405 @Override 406 final public void setup() { 407 if (!_femaleSocket.isConnected() 408 || !_femaleSocket.getConnectedSocket().getSystemName() 409 .equals(_socketSystemName)) { 410 411 _femaleSocket.disconnect(); 412 413 if (_socketSystemName != null) { 414 try { 415 MaleSocket maleSocket = 416 InstanceManager.getDefault(DigitalActionManager.class) 417 .getBySystemName(_socketSystemName); 418 if (maleSocket != null) { 419 _femaleSocket.connect(maleSocket); 420 maleSocket.setup(); 421 } else { 422 log.error("digital action is not found: {}", _socketSystemName); 423 } 424 } catch (SocketAlreadyConnectedException ex) { 425 // This shouldn't happen and is a runtime error if it does. 426 throw new RuntimeException("socket is already connected"); 427 } 428 } 429 } else { 430 _femaleSocket.setup(); 431 } 432 } 433 434 /** {@inheritDoc} */ 435 @Override 436 final public void disposeMe() { 437 _femaleSocket.dispose(); 438 } 439 440 /** {@inheritDoc} */ 441 @Override 442 public void setEnabled(boolean enable) { 443 _enabled = enable; 444 if (isActive()) { 445 LogixNG logixNG = getLogixNG(); 446 if ((logixNG != null) && logixNG.isActive()) { 447 registerListeners(); 448 if (_executeAtStartup) { 449 execute(); 450 } 451 } 452 } else { 453 unregisterListeners(); 454 } 455 } 456 457 /** {@inheritDoc} */ 458 @Override 459 public boolean isEnabled() { 460 return _enabled; 461 } 462 463 /** {@inheritDoc} */ 464 @Override 465 public void setExecuteAtStartup(boolean value) { 466 _executeAtStartup = value; 467 } 468 469 /** {@inheritDoc} */ 470 @Override 471 public boolean isExecuteAtStartup() { 472 return _executeAtStartup; 473 } 474 475 /** {@inheritDoc} */ 476 @Override 477 public synchronized boolean isListenersRegistered() { 478 return _listenersAreRegistered; 479 } 480 481 /** {@inheritDoc} */ 482 @Override 483 public synchronized void registerListenersForThisClass() { 484 _listenersAreRegistered = true; 485 } 486 487 /** {@inheritDoc} */ 488 @Override 489 public synchronized void unregisterListenersForThisClass() { 490 _listenersAreRegistered = false; 491 } 492 493 @Override 494 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) { 495 throw new UnsupportedOperationException("Not supported yet."); 496 } 497 498 @Override 499 public boolean existsInTree() { 500 return true; 501 } 502 503 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class); 504 505}