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