001package jmri.jmrit.logixng.actions; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006 007import javax.annotation.Nonnull; 008 009import jmri.*; 010import jmri.jmrit.dispatcher.*; 011import jmri.jmrit.logixng.*; 012import jmri.jmrit.logixng.util.*; 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 action triggers a Dispather ActiveTrain. 020 * 021 * @author Daniel Bergqvist Copyright 2021 022 * @author Dave Sand Copyright 2021 023 */ 024public class ActionDispatcher extends AbstractDigitalAction 025 implements PropertyChangeListener { 026 027 private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct; 028 private String _trainInfoFileName = ""; 029 private String _reference = ""; 030 private String _localVariable = ""; 031 private String _formula = ""; 032 private ExpressionNode _expressionNode; 033 034 private final LogixNG_SelectEnum<DirectOperation> _selectEnum = 035 new LogixNG_SelectEnum<>(this, DirectOperation.values(), DirectOperation.None, this); 036 037 private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct; 038 private String _dataReference = ""; 039 private String _dataLocalVariable = ""; 040 private String _dataFormula = ""; 041 private ExpressionNode _dataExpressionNode; 042 043 private boolean _resetOption = false; 044 private boolean _terminateOption = false; 045 private int _trainPriority = 5; 046 047 private final DispatcherActiveTrainManager _atManager; 048 049 050 public ActionDispatcher(String sys, String user) 051 throws BadUserNameException, BadSystemNameException { 052 super(sys, user); 053 _atManager = InstanceManager.getDefault(DispatcherActiveTrainManager.class); 054 } 055 056 @Override 057 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException { 058 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 059 String sysName = systemNames.get(getSystemName()); 060 String userName = userNames.get(getSystemName()); 061 if (sysName == null) sysName = manager.getAutoSystemName(); 062 ActionDispatcher copy = new ActionDispatcher(sysName, userName); 063 copy.setComment(getComment()); 064 065 copy.setAddressing(_addressing); 066 copy.setTrainInfoFileName(_trainInfoFileName); 067 copy.setReference(_reference); 068 copy.setLocalVariable(_localVariable); 069 copy.setFormula(_formula); 070 071 _selectEnum.copy(copy._selectEnum); 072 073 copy.setDataAddressing(_dataAddressing); 074 copy.setDataReference(_dataReference); 075 copy.setDataLocalVariable(_dataLocalVariable); 076 copy.setDataFormula(_dataFormula); 077 078 copy.setResetOption(_resetOption); 079 copy.setTerminateOption(_terminateOption); 080 copy.setTrainPriority(_trainPriority); 081 082 return manager.registerAction(copy); 083 } 084 085 public void setTrainInfoFileName(@Nonnull String fileName) { 086 _trainInfoFileName = fileName; 087 } 088 089 public String getTrainInfoFileName() { 090 return _trainInfoFileName; 091 } 092 093 094 public void setAddressing(NamedBeanAddressing addressing) throws ParserException { 095 _addressing = addressing; 096 parseFormula(); 097 } 098 099 public NamedBeanAddressing getAddressing() { 100 return _addressing; 101 } 102 103 public void setReference(@Nonnull String reference) { 104 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 105 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 106 } 107 _reference = reference; 108 } 109 110 public String getReference() { 111 return _reference; 112 } 113 114 public void setLocalVariable(@Nonnull String localVariable) { 115 _localVariable = localVariable; 116 } 117 118 public String getLocalVariable() { 119 return _localVariable; 120 } 121 122 public void setFormula(@Nonnull String formula) throws ParserException { 123 _formula = formula; 124 parseFormula(); 125 } 126 127 public String getFormula() { 128 return _formula; 129 } 130 131 private void parseFormula() throws ParserException { 132 if (_addressing == NamedBeanAddressing.Formula) { 133 Map<String, Variable> variables = new HashMap<>(); 134 135 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 136 _expressionNode = parser.parseExpression(_formula); 137 } else { 138 _expressionNode = null; 139 } 140 } 141 142 143 public LogixNG_SelectEnum<DirectOperation> getSelectEnum() { 144 return _selectEnum; 145 } 146 147 148 public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException { 149 _dataAddressing = addressing; 150 parseDataFormula(); 151 } 152 153 public NamedBeanAddressing getDataAddressing() { 154 return _dataAddressing; 155 } 156 157 public void setDataReference(@Nonnull String reference) { 158 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 159 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 160 } 161 _dataReference = reference; 162 } 163 164 public String getDataReference() { 165 return _dataReference; 166 } 167 168 public void setDataLocalVariable(@Nonnull String localVariable) { 169 _dataLocalVariable = localVariable; 170 } 171 172 public String getDataLocalVariable() { 173 return _dataLocalVariable; 174 } 175 176 public void setDataFormula(@Nonnull String formula) throws ParserException { 177 _dataFormula = formula; 178 parseDataFormula(); 179 } 180 181 public String getDataFormula() { 182 return _dataFormula; 183 } 184 185 private void parseDataFormula() throws ParserException { 186 if (_dataAddressing == NamedBeanAddressing.Formula) { 187 Map<String, Variable> variables = new HashMap<>(); 188 189 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 190 _dataExpressionNode = parser.parseExpression(_dataFormula); 191 } else { 192 _dataExpressionNode = null; 193 } 194 } 195 196 197 public void setTrainPriority(int trainPriority) { 198 _trainPriority = trainPriority; 199 } 200 201 public int getTrainPriority() { 202 return _trainPriority; 203 } 204 205 public void setResetOption(boolean resetOption) { 206 _resetOption = resetOption; 207 } 208 209 public boolean getResetOption() { 210 return _resetOption; 211 } 212 213 public void setTerminateOption(boolean terminateOption) { 214 _terminateOption = terminateOption; 215 } 216 217 public boolean getTerminateOption() { 218 return _terminateOption; 219 } 220 221 /** {@inheritDoc} */ 222 @Override 223 public Category getCategory() { 224 return Category.ITEM; 225 } 226 227 private String getNewData(DirectOperation oper) throws JmriException { 228 switch (_dataAddressing) { 229 case Direct: 230 switch(oper) { 231 case TrainPriority: 232 return String.valueOf(getTrainPriority()); 233 234 case ResetWhenDoneOption: 235 return getResetOption() ? "true" : "false"; 236 237 case TerminateWhenDoneOption: 238 return getTerminateOption() ? "true" : "false"; 239 240 default: 241 return ""; 242 } 243 244 case Reference: 245 return ReferenceUtil.getReference( 246 getConditionalNG().getSymbolTable(), _dataReference); 247 248 case LocalVariable: 249 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 250 return TypeConversionUtil 251 .convertToString(symbolTable.getValue(_dataLocalVariable), false); 252 253 case Formula: 254 return _dataExpressionNode != null 255 ? TypeConversionUtil.convertToString( 256 _dataExpressionNode.calculate( 257 getConditionalNG().getSymbolTable()), false) 258 : ""; 259 260 default: 261 throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name()); 262 } 263 } 264 265 266 /** {@inheritDoc} */ 267 @Override 268 public void execute() throws JmriException { 269 String trainInfoFileName = ""; 270 271 switch (_addressing) { 272 case Direct: 273 trainInfoFileName = _trainInfoFileName; 274 break; 275 276 case Reference: 277 trainInfoFileName = ReferenceUtil.getReference( 278 getConditionalNG().getSymbolTable(), _reference); 279 break; 280 281 case LocalVariable: 282 SymbolTable symbolTable = getConditionalNG().getSymbolTable(); 283 trainInfoFileName = TypeConversionUtil 284 .convertToString(symbolTable.getValue(_localVariable), false); 285 break; 286 287 case Formula: 288 trainInfoFileName = _expressionNode != null ? 289 TypeConversionUtil.convertToString(_expressionNode.calculate( 290 getConditionalNG().getSymbolTable()), false) 291 : ""; 292 break; 293 294 default: 295 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 296 } 297 298 if (trainInfoFileName.isEmpty()) { 299 return; 300 } 301 302 ActiveTrain activeTrain = _atManager.getActiveTrain(trainInfoFileName); 303 304 DirectOperation oper = _selectEnum.evaluateEnum(getConditionalNG()); 305 306 String newData = getNewData(oper); 307 308 switch (oper) { 309 case LoadTrainFromFile: 310 if (activeTrain == null) { 311 activeTrain = _atManager.createActiveTrain(trainInfoFileName); 312 if (activeTrain == null) { 313 log.warn("DispatcherAction: Unable to create an active train"); 314 } 315 } else { 316 log.warn("DispatcherAction: The active train already exists"); 317 } 318 return; 319 320 case TerminateTrain: 321 _atManager.terminateActiveTrain(trainInfoFileName); 322 return; 323 324 case TrainPriority: 325 if (activeTrain != null) { 326 int newInt = Integer.parseInt(newData); 327 if (newInt < 0) newInt = 0; 328 if (newInt > 100) newInt = 100; 329 activeTrain.setPriority(newInt); 330 } 331 return; 332 333 case ResetWhenDoneOption: 334 if (activeTrain != null) { 335 if (newData.equals("true") || newData.equals("false")) { 336 boolean reset = newData.equals("true"); 337 activeTrain.setResetWhenDone(reset); 338 } 339 } 340 return; 341 342 case TerminateWhenDoneOption: 343 if (activeTrain != null) { 344 if (newData.equals("true") || newData.equals("false")) { 345 boolean term = newData.equals("true"); 346 activeTrain.setTerminateWhenDone(term); 347 } 348 } 349 return; 350 351 default: 352 throw new IllegalArgumentException("invalid oper state: " + oper.name()); 353 } 354 } 355 356 @Override 357 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 358 throw new UnsupportedOperationException("Not supported."); 359 } 360 361 @Override 362 public int getChildCount() { 363 return 0; 364 } 365 366 @Override 367 public String getShortDescription(Locale locale) { 368 return Bundle.getMessage(locale, "ActionDispatcher_Short"); 369 } 370 371 @Override 372 public String getLongDescription(Locale locale) { 373 374// Start train using train info file {abc.xml} 375// Terminate train {transit/name} 376// Set priority for train {} to {nnn} 377// {[Enable|Disable]} "reset when done" for train {}} 378// {[Enable|Disable]} "terminate when done" for train using {}} 379 380 String fileName; 381 String state = _selectEnum.getDescription(locale); 382 383 switch (_addressing) { 384 case Direct: 385 fileName = Bundle.getMessage(locale, "AddressByDirect", _trainInfoFileName); 386 break; 387 388 case Reference: 389 fileName = Bundle.getMessage(locale, "AddressByReference", _reference); 390 break; 391 392 case LocalVariable: 393 fileName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable); 394 break; 395 396 case Formula: 397 fileName = Bundle.getMessage(locale, "AddressByFormula", _formula); 398 break; 399 400 default: 401 throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name()); 402 } 403 404 if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) { 405 switch (_selectEnum.getEnum()) { 406 case LoadTrainFromFile: 407 return Bundle.getMessage("ActionDispatcher_Long_LoadTrain", fileName); 408 409 case TerminateTrain: 410 return Bundle.getMessage("ActionDispatcher_Long_Terminate", fileName); 411 412 case TrainPriority: 413 return getLongDataDescription(locale, "ActionDispatcher_Long_Priority", 414 fileName, String.valueOf(getTrainPriority())); 415 case ResetWhenDoneOption: 416 return getLongDataDescription(locale, "ActionDispatcher_Long_ResetOption", 417 fileName, getResetOption() ? Bundle.getMessage("ActionDispatcher_Long_Enable") : 418 Bundle.getMessage("ActionDispatcher_Long_Disable")); 419 case TerminateWhenDoneOption: 420 return getLongDataDescription(locale, "ActionDispatcher_Long_TerminateOption", 421 fileName, getTerminateOption() ? Bundle.getMessage("ActionDispatcher_Long_Enable") : 422 Bundle.getMessage("ActionDispatcher_Long_Disable")); 423 default: 424 throw new IllegalArgumentException("invalid enum: " + _selectEnum.getEnum().name()); 425 426 } 427 } 428 429 return Bundle.getMessage(locale, "ActionDispatcher_Long", fileName, state); 430 } 431 432 private String getLongDataDescription(Locale locale, String bundleKey, String fileName, String value) { 433 switch (_dataAddressing) { 434 case Direct: 435 return Bundle.getMessage(locale, bundleKey, fileName, value); 436 case Reference: 437 return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByReference", _dataReference)); 438 case LocalVariable: 439 return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable)); 440 case Formula: 441 return Bundle.getMessage(locale, bundleKey, fileName, Bundle.getMessage("AddressByFormula", _dataFormula)); 442 default: 443 throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name()); 444 } 445 } 446 447 /** {@inheritDoc} */ 448 @Override 449 public void setup() { 450 // Do nothing 451 } 452 453 /** {@inheritDoc} */ 454 @Override 455 public void registerListenersForThisClass() { 456 _selectEnum.registerListeners(); 457 } 458 459 /** {@inheritDoc} */ 460 @Override 461 public void unregisterListenersForThisClass() { 462 _selectEnum.unregisterListeners(); 463 } 464 465 /** {@inheritDoc} */ 466 @Override 467 public void disposeMe() { 468 } 469 470 public enum DirectOperation { 471 None(""), 472 LoadTrainFromFile(Bundle.getMessage("ActionDispatcher_LoadTrainFromFile")), 473 TerminateTrain(Bundle.getMessage("ActionDispatcher_TerminateTrain")), 474 TrainPriority(Bundle.getMessage("ActionDispatcher_TrainPriority")), 475 ResetWhenDoneOption(Bundle.getMessage("ActionDispatcher_ResetWhenDoneOption")), 476 TerminateWhenDoneOption(Bundle.getMessage("ActionDispatcher_TerminateWhenDoneOption")); 477 478 private final String _text; 479 480 private DirectOperation(String text) { 481 this._text = text; 482 } 483 484 @Override 485 public String toString() { 486 return _text; 487 } 488 489 } 490 491 /** {@inheritDoc} */ 492 @Override 493 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 494 } 495 496 /** {@inheritDoc} */ 497 @Override 498 public void propertyChange(PropertyChangeEvent evt) { 499 getConditionalNG().execute(); 500 } 501 502 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionDispatcher.class); 503 504}