001package jmri.jmrit.logixng.expressions; 002 003import java.beans.*; 004import java.util.*; 005 006import javax.annotation.Nonnull; 007 008import jmri.*; 009import jmri.jmrit.dispatcher.*; 010import jmri.jmrit.logixng.*; 011import jmri.jmrit.logixng.util.DispatcherActiveTrainManager; 012import jmri.jmrit.logixng.util.ReferenceUtil; 013import jmri.jmrit.logixng.util.parser.*; 014import jmri.jmrit.logixng.util.parser.ExpressionNode; 015import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 016import jmri.util.TypeConversionUtil; 017 018/** 019 * This expression checks the status or mode of an active train. 020 * <p> 021 * A Dispatcher ActiveTrain is a transient object. The DispatcherActiveTrainManager is a special 022 * LogiNG class which manages the relationships with expressions using the Dispatcher TrainInfo file 023 * as the key. This makes it possible to add and remove ActiveTrain PropertyChange listeners. 024 * 025 * @author Dave Sand Copyright 2021 026 */ 027public class ExpressionDispatcher extends AbstractDigitalExpression 028 implements PropertyChangeListener { 029 030 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 031 private String _reference = ""; 032 private String _localVariable = ""; 033 private String _formula = ""; 034 private ExpressionNode _expressionNode; 035 036 private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct; 037 private String _stateReference = ""; 038 private String _stateLocalVariable = ""; 039 private String _stateFormula = ""; 040 private ExpressionNode _stateExpressionNode; 041 042 private String _trainInfoFileName = ""; 043 private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is; 044 private DispatcherState _dispatcherState = DispatcherState.Mode; 045 046 private final DispatcherActiveTrainManager _atManager; 047 private boolean _activeTrainListeners = false; 048 049 /** 050 * An active train is transient. It can be terminated manually which means the reference 051 * will no longer be valid. 052 */ 053 private ActiveTrain _activeTrain = null; 054 055 056 public ExpressionDispatcher(String sys, String user) 057 throws BadUserNameException, BadSystemNameException { 058 super(sys, user); 059 _atManager = InstanceManager.getDefault(DispatcherActiveTrainManager.class); 060 } 061 062 @Override 063 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 064 DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class); 065 String sysName = systemNames.get(getSystemName()); 066 String userName = userNames.get(getSystemName()); 067 if (sysName == null) sysName = manager.getAutoSystemName(); 068 ExpressionDispatcher copy = new ExpressionDispatcher(sysName, userName); 069 copy.setComment(getComment()); 070 071 copy.setTrainInfoFileName(_trainInfoFileName); 072 copy.setBeanState(getBeanState()); 073 074 copy.setBeanState(_dispatcherState); 075 copy.setAddressing(_addressing); 076 copy.setFormula(_formula); 077 copy.setLocalVariable(_localVariable); 078 copy.setReference(_reference); 079 080 copy.set_Is_IsNot(_is_IsNot); 081 082 copy.setStateAddressing(_stateAddressing); 083 copy.setStateFormula(_stateFormula); 084 copy.setStateLocalVariable(_stateLocalVariable); 085 copy.setStateReference(_stateReference); 086 087 088 return manager.registerExpression(copy); 089 } 090 091 092 public void setTrainInfoFileName(@Nonnull String fileName) { 093 _trainInfoFileName = fileName; 094 } 095 096 public String getTrainInfoFileName() { 097 return _trainInfoFileName; 098 } 099 100 101 public void setAddressing(NamedBeanAddressing addressing) throws ParserException { 102 _addressing = addressing; 103 parseFormula(); 104 } 105 106 public NamedBeanAddressing getAddressing() { 107 return _addressing; 108 } 109 110 public void setReference(@Nonnull String reference) { 111 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 112 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 113 } 114 _reference = reference; 115 } 116 117 public String getReference() { 118 return _reference; 119 } 120 121 public void setLocalVariable(@Nonnull String localVariable) { 122 _localVariable = localVariable; 123 } 124 125 public String getLocalVariable() { 126 return _localVariable; 127 } 128 129 public void setFormula(@Nonnull String formula) throws ParserException { 130 _formula = formula; 131 parseFormula(); 132 } 133 134 public String getFormula() { 135 return _formula; 136 } 137 138 private void parseFormula() throws ParserException { 139 if (_addressing == NamedBeanAddressing.Formula) { 140 Map<String, Variable> variables = new HashMap<>(); 141 142 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 143 _expressionNode = parser.parseExpression(_formula); 144 } else { 145 _expressionNode = null; 146 } 147 } 148 149 150 public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) { 151 _is_IsNot = is_IsNot; 152 } 153 154 public Is_IsNot_Enum get_Is_IsNot() { 155 return _is_IsNot; 156 } 157 158 159 public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException { 160 _stateAddressing = addressing; 161 parseStateFormula(); 162 } 163 164 public NamedBeanAddressing getStateAddressing() { 165 return _stateAddressing; 166 } 167 168 public void setStateReference(@Nonnull String reference) { 169 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 170 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 171 } 172 _stateReference = reference; 173 } 174 175 public String getStateReference() { 176 return _stateReference; 177 } 178 179 public void setStateLocalVariable(@Nonnull String localVariable) { 180 _stateLocalVariable = localVariable; 181 } 182 183 public String getStateLocalVariable() { 184 return _stateLocalVariable; 185 } 186 187 public void setStateFormula(@Nonnull String formula) throws ParserException { 188 _stateFormula = formula; 189 parseStateFormula(); 190 } 191 192 public String getStateFormula() { 193 return _stateFormula; 194 } 195 196 private void parseStateFormula() throws ParserException { 197 if (_stateAddressing == NamedBeanAddressing.Formula) { 198 Map<String, Variable> variables = new HashMap<>(); 199 200 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 201 _stateExpressionNode = parser.parseExpression(_stateFormula); 202 } else { 203 _stateExpressionNode = null; 204 } 205 } 206 207 208 public void setBeanState(DispatcherState state) { 209 _dispatcherState = state; 210 } 211 212 public DispatcherState getBeanState() { 213 return _dispatcherState; 214 } 215 216 217 /** {@inheritDoc} */ 218 @Override 219 public Category getCategory() { 220 return Category.ITEM; 221 } 222 223 private String getSelectedFileName() throws JmriException { 224 switch (_addressing) { 225 case Direct: 226 return getTrainInfoFileName(); 227 228 case Reference: 229 return ReferenceUtil.getReference( 230 getConditionalNG().getSymbolTable(), _reference); 231 232 case LocalVariable: 233 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 234 return TypeConversionUtil 235 .convertToString(symbolTable.getValue(_localVariable), false); 236 237 case Formula: 238 return _expressionNode != null ? 239 TypeConversionUtil.convertToString(_expressionNode.calculate( 240 getConditionalNG().getSymbolTable()), false) 241 : ""; 242 243 default: 244 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 245 } 246 } 247 248 private String getNewState() throws JmriException { 249 250 switch (_stateAddressing) { 251 case Reference: 252 return ReferenceUtil.getReference( 253 getConditionalNG().getSymbolTable(), _stateReference); 254 255 case LocalVariable: 256 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 257 return TypeConversionUtil 258 .convertToString(symbolTable.getValue(_stateLocalVariable), false); 259 260 case Formula: 261 return _stateExpressionNode != null 262 ? TypeConversionUtil.convertToString( 263 _stateExpressionNode.calculate( 264 getConditionalNG().getSymbolTable()), false) 265 : null; 266 267 default: 268 throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name()); 269 } 270 } 271 272 /** {@inheritDoc} */ 273 @Override 274 public boolean evaluate() throws JmriException { 275 String trainInfoFileName = getSelectedFileName(); 276 277 if (trainInfoFileName.isEmpty()) { 278 return false; 279 } 280 281 DispatcherState checkDispatcherState; 282 if ((_stateAddressing == NamedBeanAddressing.Direct)) { 283 checkDispatcherState = _dispatcherState; 284 } else { 285 checkDispatcherState = DispatcherState.valueOf(getNewState()); 286 } 287 288 boolean result = false; 289 290 switch (checkDispatcherState) { 291 case Automatic: 292 if (_activeTrain != null) result = (_activeTrain.getMode() == ActiveTrain.AUTOMATIC); 293 break; 294 case Dispatched: 295 if (_activeTrain != null) result = (_activeTrain.getMode() == ActiveTrain.DISPATCHED); 296 break; 297 case Manual: 298 if (_activeTrain != null) result = (_activeTrain.getMode() == ActiveTrain.MANUAL); 299 break; 300 case Running: 301 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.RUNNING); 302 break; 303 case Paused: 304 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.PAUSED); 305 break; 306 case Waiting: 307 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.WAITING); 308 break; 309 case Working: 310 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.WORKING); 311 break; 312 case Ready: 313 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.READY); 314 break; 315 case Stopped: 316 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.STOPPED); 317 break; 318 case Done: 319 if (_activeTrain != null) result = (_activeTrain.getStatus() == ActiveTrain.DONE); 320 break; 321 322 default: 323 throw new UnsupportedOperationException("checkDispatcherState has unknown value: " + checkDispatcherState.name()); 324 } 325 326 if (_is_IsNot == Is_IsNot_Enum.Is) { 327 return result; 328 } else { 329 return !result; 330 } 331 } 332 333 @Override 334 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 335 throw new UnsupportedOperationException("Not supported."); 336 } 337 338 @Override 339 public int getChildCount() { 340 return 0; 341 } 342 343 344 @Override 345 public String getShortDescription(Locale locale) { 346 return Bundle.getMessage(locale, "Dispatcher_Short"); 347 } 348 349 @Override 350 public String getLongDescription(Locale locale) { 351 String fileName; 352 String state; 353 354 switch (_addressing) { 355 case Direct: 356 fileName = Bundle.getMessage(locale, "AddressByDirect", _trainInfoFileName); 357 break; 358 359 case Reference: 360 fileName = Bundle.getMessage(locale, "AddressByReference", _reference); 361 break; 362 363 case LocalVariable: 364 fileName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 365 break; 366 367 case Formula: 368 fileName = Bundle.getMessage(locale, "AddressByFormula", _formula); 369 break; 370 371 default: 372 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 373 } 374 375 switch (_stateAddressing) { 376 case Direct: 377 state = Bundle.getMessage(locale, "AddressByDirect", _dispatcherState._text); 378 break; 379 380 case Reference: 381 state = Bundle.getMessage(locale, "AddressByReference", _stateReference); 382 break; 383 384 case LocalVariable: 385 state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable); 386 break; 387 388 case Formula: 389 state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula); 390 break; 391 392 default: 393 throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name()); 394 } 395 396 return Bundle.getMessage(locale, "Dispatcher_Long", fileName, _is_IsNot.toString(), state); 397 } 398 399 /** {@inheritDoc} */ 400 @Override 401 public void setup() { 402 // Do nothing 403 } 404 405 /** {@inheritDoc} */ 406 @Override 407 public void registerListenersForThisClass() { 408 if (! _listenersAreRegistered) { 409 _atManager.addPropertyChangeListener(this); 410 _listenersAreRegistered = true; 411 } 412 } 413 414 /** {@inheritDoc} */ 415 @Override 416 public void unregisterListenersForThisClass() { 417 if (_listenersAreRegistered) { 418 _atManager.removePropertyChangeListener(this); 419 _listenersAreRegistered = false; 420 } 421 } 422 423 /** {@inheritDoc} 424 * ActiveTrain is created by DispatcherActiveTrainManager 425 * status and mode are created by Dispatcher ActiveTrain 426 */ 427 @Override 428 public void propertyChange(PropertyChangeEvent evt) { 429 switch (evt.getPropertyName()) { 430 case "ActiveTrain": 431 manageActiveTrain(evt); 432 break; 433 434 case "mode": 435 if ((int) evt.getNewValue() == ActiveTrain.TERMINATED) { 436 // The Dispatcher active train was terminated by an external process. 437 // Force the manager to update the LogixNG active train map. 438 _atManager.getActiveTrain(_trainInfoFileName); 439 return; 440 } 441 442 getConditionalNG().execute(); 443 break; 444 445 case "status": 446 getConditionalNG().execute(); 447 break; 448 449 default: 450 log.debug("Other property changes are ignored: name = {}", evt.getPropertyName()); 451 } 452 } 453 454 /** 455 * The DispatcherActiveTrainManager keeps track of the ActiveTrains created using LogixNG. 456 * When an ActiveTrain is added, ActiveTrain property change listeners are added so that the 457 * expression can be notified of status and mode changes. _activeTrain is updated with the 458 * with current active train. 459 * <p> 460 * When an ActiveTrain is removed, the listeners are removed and _activeTrain is set to null. 461 * @param event The DispatcherActiveTrainManager property change event. 462 */ 463 private void manageActiveTrain(PropertyChangeEvent event) { 464 String selectedFileName; 465 try { 466 selectedFileName = getSelectedFileName(); 467 } catch (JmriException ex) { 468 log.warn("Unexpected exception, using Direct file name"); 469 selectedFileName = _trainInfoFileName; 470 } 471 472 String eventFileName = (String) event.getOldValue(); 473 if (eventFileName.isEmpty()) { 474 eventFileName = (String) event.getNewValue(); 475 } 476 477 if (! selectedFileName.equals(eventFileName)) return; 478 479 ActiveTrain checkTrain = _atManager.getActiveTrain(selectedFileName); 480 481 if (checkTrain == null) { 482 if (_activeTrain != null) { 483 if (_activeTrainListeners) { 484 _activeTrain.removePropertyChangeListener(this); 485 _activeTrainListeners = false; 486 } 487 _activeTrain = null; 488 } 489 return; 490 } 491 492 if (checkTrain == _activeTrain) return; 493 494 if (_activeTrain == null) { 495 _activeTrain = checkTrain; 496 _activeTrainListeners = false; 497 } 498 499 if (! _activeTrainListeners) { 500 _activeTrain.addPropertyChangeListener(this); 501 _activeTrainListeners = true; 502 } 503 } 504 505 506 /** {@inheritDoc} */ 507 @Override 508 public void disposeMe() { 509 } 510 511 512 public enum DispatcherState { 513 Mode(Bundle.getMessage("DispatcherSeparatorMode"), "Separator"), 514 Automatic(Bundle.getMessage("DispatcherMode_Automatic"), "Mode"), 515 Dispatched(Bundle.getMessage("DispatcherMode_Dispatched"), "Mode"), 516 Manual(Bundle.getMessage("DispatcherMode_Manual"), "Mode"), 517 Status(Bundle.getMessage("DispatcherSeparatorStatus"), "Separator"), 518 Running(Bundle.getMessage("DispatcherStatus_Running"), "Status"), 519 Paused(Bundle.getMessage("DispatcherStatus_Paused"), "Status"), 520 Waiting(Bundle.getMessage("DispatcherStatus_Waiting"), "Status"), 521 Working(Bundle.getMessage("DispatcherStatus_Working"), "Status"), 522 Ready(Bundle.getMessage("DispatcherStatus_Ready"), "Status"), 523 Stopped(Bundle.getMessage("DispatcherStatus_Stopped"), "Status"), 524 Done(Bundle.getMessage("DispatcherStatus_Done"), "Status"); 525 526 private final String _text; 527 private final String _type; 528 529 private DispatcherState(String text, String type) { 530 this._text = text; 531 this._type = type; 532 } 533 534 public String getType() { 535 return _type; 536 } 537 538 @Override 539 public String toString() { 540 return _text; 541 } 542 543 } 544 545 546 /** {@inheritDoc} */ 547 @Override 548 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 549 log.debug("getUsageReport :: ExpressionDispatcher: bean = {}, report = {}", cdl, report); 550 } 551 552 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionDispatcher.class); 553 554}