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