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.ThreadingUtil; 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 * @throws IllegalArgumentException when needed 149 */ 150 public static void executeModule(Module module, Map<String, Object> parameters) 151 throws IllegalArgumentException { 152 153 if (module == null) { 154 throw new IllegalArgumentException("The parameter \"module\" is null"); 155 } 156 if (!(module.getRootSocket() instanceof DefaultFemaleDigitalActionSocket)) { 157 throw new IllegalArgumentException("The module " + module.getDisplayName() + " is not a DigitalActionModule"); 158 } 159 if (parameters == null) { 160 throw new IllegalArgumentException("The parameter \"parameters\" is null"); 161 } 162 163 LogixNG_Thread thread = LogixNG_Thread.getThread(LogixNG_Thread.DEFAULT_LOGIXNG_THREAD); 164 ConditionalNG conditionalNG = new DefaultConditionalNG("IQC0000000", null); 165 InternalFemaleSocket socket = new InternalFemaleSocket(conditionalNG, module, parameters); 166 thread.runOnLogixNGEventually(() -> { internalExecute(conditionalNG, socket); }); 167 } 168 169 private static class InternalFemaleSocket extends DefaultFemaleDigitalActionSocket { 170 171 private final ConditionalNG _conditionalNG; 172 private final Module _module; 173 private final Map<String, Object> _parameters; 174 175 public InternalFemaleSocket(ConditionalNG conditionalNG, Module module, Map<String, Object> parameters) { 176 super(null, new FemaleSocketListener(){ 177 @Override 178 public void connected(FemaleSocket socket) { 179 // Do nothing 180 } 181 182 @Override 183 public void disconnected(FemaleSocket socket) { 184 // Do nothing 185 } 186 }, "A"); 187 _conditionalNG = conditionalNG; 188 _module = module; 189 _parameters = parameters; 190 } 191 192 @Override 193 public void execute() throws JmriException { 194 FemaleSocket socket = _module.getRootSocket(); 195 if (!(socket instanceof DefaultFemaleDigitalActionSocket)) { 196 throw new IllegalArgumentException("The module " + _module.getDisplayName() + " is not a DigitalActionModule"); 197 } 198 199 synchronized(this) { 200 SymbolTable oldSymbolTable = _conditionalNG.getSymbolTable(); 201 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(_conditionalNG); 202 List<Module.ParameterData> _parameterData = new ArrayList<>(); 203 for (Module.Parameter p : _module.getParameters()) { 204 _parameterData.add(new Module.ParameterData( 205 p.getName(), SymbolTable.InitialValueType.None, "", 206 Module.ReturnValueType.None, "")); 207 } 208 newSymbolTable.createSymbols(_conditionalNG.getSymbolTable(), _parameterData); 209 for (var entry : _parameters.entrySet()) { 210 newSymbolTable.setValue(entry.getKey(), entry.getValue()); 211 } 212 _conditionalNG.setSymbolTable(newSymbolTable); 213 214 ((DefaultFemaleDigitalActionSocket)socket).execute(); 215 _conditionalNG.setSymbolTable(oldSymbolTable); 216 } 217 } 218 } 219 220 private static void internalExecute(ConditionalNG conditionalNG, FemaleDigitalActionSocket femaleSocket) { 221 if (conditionalNG.isEnabled()) { 222 DefaultSymbolTable newSymbolTable = new DefaultSymbolTable(conditionalNG); 223 224 try { 225 conditionalNG.setCurrentConditionalNG(conditionalNG); 226 227 conditionalNG.setSymbolTable(newSymbolTable); 228 229 LogixNG logixNG = conditionalNG.getLogixNG(); 230 InlineLogixNG inlineLogixNG = null; 231 if (logixNG != null) { 232 inlineLogixNG = logixNG.getInlineLogixNG(); 233 } 234 if (inlineLogixNG != null) { 235 List<SymbolTable.VariableData> localVariables = new ArrayList<>(); 236 localVariables.add(new SymbolTable.VariableData( 237 "__InlineLogixNG__", SymbolTable.InitialValueType.String, 238 inlineLogixNG.getNameString())); 239// localVariables.add(new SymbolTable.VariableData( 240// "__PositionableId__", SymbolTable.InitialValueType.String, 241// inlineLogixNG.getId())); 242 localVariables.add(new SymbolTable.VariableData( 243 "__Editor__", SymbolTable.InitialValueType.String, 244 inlineLogixNG.getEditorName())); 245 newSymbolTable.createSymbols(localVariables); 246 } 247 248 if (femaleSocket != null) { 249 femaleSocket.execute(); 250 } else { 251 conditionalNG.getFemaleSocket().execute(); 252 } 253 } catch (AbortConditionalNG_IgnoreException | ReturnException | ExitException e) { 254 // A AbortConditionalNG_IgnoreException should be ignored. 255 // A Return action in a ConditionalNG causes a ReturnException so this is okay. 256 // An Exit action in a ConditionalNG causes a ExitException so this is okay. 257 } catch (PassThruException e) { 258 // This happens due to a a Break action or a Continue action that isn't handled. 259 log.info("ConditionalNG {} was aborted during execute: {}", 260 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 261 } catch (AbortConditionalNGExecutionException e) { 262 if (InstanceManager.getDefault(LogixNGPreferences.class).getShowSystemNameInException()) { 263 log.warn("ConditionalNG {} was aborted during execute in the item {}: {}", 264 conditionalNG.getSystemName(), e.getMaleSocket().getSystemName(), e.getCause(), e.getCause()); 265 } else { 266 log.warn("ConditionalNG {} was aborted during execute: {}", 267 conditionalNG.getSystemName(), e.getCause(), e.getCause()); 268 } 269 } catch (JmriException | RuntimeException e) { 270// LoggingUtil.warnOnce(log, "ConditionalNG {} got an exception during execute: {}", 271// conditionalNG.getSystemName(), e, e); 272 log.warn("ConditionalNG {} got an exception during execute: {}", 273 conditionalNG.getSystemName(), e, e); 274 } 275 276 conditionalNG.setSymbolTable(newSymbolTable.getPrevSymbolTable()); 277 } 278 } 279 280 private static class ExecuteTask implements ThreadingUtil.ThreadAction { 281 282 private final ConditionalNG _conditionalNG; 283 private final ExecuteLock _executeLock; 284 private final FemaleDigitalActionSocket _localFemaleSocket; 285 286 public ExecuteTask(ConditionalNG conditionalNG, ExecuteLock executeLock, FemaleDigitalActionSocket femaleSocket) { 287 _conditionalNG = conditionalNG; 288 _executeLock = executeLock; 289 _localFemaleSocket = femaleSocket; 290 } 291 292 @Override 293 public void run() { 294 while (_executeLock.loop()) { 295 internalExecute(_conditionalNG, _localFemaleSocket); 296 } 297 } 298 299 } 300 301 /** 302 * Set the current ConditionalNG. 303 * @param conditionalNG the current ConditionalNG 304 */ 305 @Override 306 public void setCurrentConditionalNG(ConditionalNG conditionalNG) { 307 if (this != conditionalNG) { 308 throw new UnsupportedOperationException("The new conditionalNG must be the same as myself"); 309 } 310 for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) { 311 m.setCurrentConditionalNG(conditionalNG); 312 } 313 } 314 315 /** {@inheritDoc} */ 316 @Override 317 public Stack getStack() { 318 return _stack; 319 } 320 321 /** {@inheritDoc} */ 322 @Override 323 public SymbolTable getSymbolTable() { 324 return _symbolTable; 325 } 326 327 /** {@inheritDoc} */ 328 @Override 329 public void setSymbolTable(SymbolTable symbolTable) { 330 _symbolTable = symbolTable; 331 } 332 333 @Override 334 public String getBeanType() { 335 return Bundle.getMessage("BeanNameConditionalNG"); 336 } 337 338 @Override 339 public void setState(int s) throws JmriException { 340 log.warn("Unexpected call to setState in DefaultConditionalNG."); // NOI18N 341 } 342 343 @Override 344 public int getState() { 345 log.warn("Unexpected call to getState in DefaultConditionalNG."); // NOI18N 346 return UNKNOWN; 347 } 348 349 @Override 350 public void connected(FemaleSocket socket) { 351 _socketSystemName = socket.getConnectedSocket().getSystemName(); 352 } 353 354 @Override 355 public void disconnected(FemaleSocket socket) { 356 _socketSystemName = null; 357 } 358 359 @Override 360 public String getShortDescription(Locale locale) { 361 return "ConditionalNG: "+getDisplayName(); 362 } 363 364 @Override 365 public String getLongDescription(Locale locale) { 366 if (_thread.getThreadId() != LogixNG_Thread.DEFAULT_LOGIXNG_THREAD) { 367 return "ConditionalNG: "+getDisplayName() + " on thread " + _thread.getThreadName(); 368 } 369 return "ConditionalNG: "+getDisplayName(); 370 } 371 372 @Override 373 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 374 if (index != 0) { 375 throw new IllegalArgumentException( 376 String.format("index has invalid value: %d", index)); 377 } 378 379 return _femaleSocket; 380 } 381 382 @Override 383 public int getChildCount() { 384 return 1; 385 } 386 387 @Override 388 public Category getCategory() { 389 throw new UnsupportedOperationException("Not supported."); 390 } 391 392 @Override 393 public void setSocketSystemName(String systemName) { 394 if ((systemName == null) || (!systemName.equals(_socketSystemName))) { 395 _femaleSocket.disconnect(); 396 } 397 _socketSystemName = systemName; 398 } 399 400 @Override 401 public String getSocketSystemName() { 402 return _socketSystemName; 403 } 404 405 /** {@inheritDoc} */ 406 @Override 407 final public void setup() { 408 if (!_femaleSocket.isConnected() 409 || !_femaleSocket.getConnectedSocket().getSystemName() 410 .equals(_socketSystemName)) { 411 412 _femaleSocket.disconnect(); 413 414 if (_socketSystemName != null) { 415 try { 416 MaleSocket maleSocket = 417 InstanceManager.getDefault(DigitalActionManager.class) 418 .getBySystemName(_socketSystemName); 419 if (maleSocket != null) { 420 _femaleSocket.connect(maleSocket); 421 maleSocket.setup(); 422 } else { 423 log.error("digital action is not found: {}", _socketSystemName); 424 } 425 } catch (SocketAlreadyConnectedException ex) { 426 // This shouldn't happen and is a runtime error if it does. 427 throw new RuntimeException("socket is already connected"); 428 } 429 } 430 } else { 431 _femaleSocket.setup(); 432 } 433 } 434 435 /** {@inheritDoc} */ 436 @Override 437 final public void disposeMe() { 438 _femaleSocket.dispose(); 439 } 440 441 /** {@inheritDoc} */ 442 @Override 443 public void setEnabled(boolean enable) { 444 _enabled = enable; 445 if (isActive()) { 446 LogixNG logixNG = getLogixNG(); 447 if ((logixNG != null) && logixNG.isActive()) { 448 registerListeners(); 449 if (_executeAtStartup) { 450 execute(); 451 } 452 } 453 } else { 454 unregisterListeners(); 455 } 456 } 457 458 /** {@inheritDoc} */ 459 @Override 460 public boolean isEnabled() { 461 return _enabled; 462 } 463 464 /** {@inheritDoc} */ 465 @Override 466 public void setExecuteAtStartup(boolean value) { 467 _executeAtStartup = value; 468 } 469 470 /** {@inheritDoc} */ 471 @Override 472 public boolean isExecuteAtStartup() { 473 return _executeAtStartup; 474 } 475 476 /** {@inheritDoc} */ 477 @Override 478 public synchronized boolean isListenersRegistered() { 479 return _listenersAreRegistered; 480 } 481 482 /** {@inheritDoc} */ 483 @Override 484 public synchronized void registerListenersForThisClass() { 485 _listenersAreRegistered = true; 486 } 487 488 /** {@inheritDoc} */ 489 @Override 490 public synchronized void unregisterListenersForThisClass() { 491 _listenersAreRegistered = false; 492 } 493 494 @Override 495 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) { 496 throw new UnsupportedOperationException("Not supported yet."); 497 } 498 499 @Override 500 public boolean existsInTree() { 501 return true; 502 } 503 504 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditionalNG.class); 505 506}