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.logix.Warrant; 011import jmri.jmrit.logix.WarrantManager; 012import jmri.jmrit.logixng.*; 013import jmri.jmrit.logixng.util.*; 014import jmri.jmrit.logixng.util.ReferenceUtil; 015import jmri.jmrit.logixng.util.parser.*; 016import jmri.jmrit.logixng.util.parser.ExpressionNode; 017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser; 018import jmri.util.ThreadingUtil; 019import jmri.util.TypeConversionUtil; 020 021/** 022 * This action triggers a warrant. 023 * 024 * @author Daniel Bergqvist Copyright 2021 025 * @author Dave Sand Copyright 2021 026 * @author Pete Cressman Copyright (C) 2022 027 */ 028public class ActionWarrant extends AbstractDigitalAction 029 implements PropertyChangeListener { 030 031 private final LogixNG_SelectNamedBean<Warrant> _selectNamedBean = 032 new LogixNG_SelectNamedBean<>( 033 this, Warrant.class, InstanceManager.getDefault(WarrantManager.class), this); 034 035 private final LogixNG_SelectEnum<DirectOperation> _selectEnum = 036 new LogixNG_SelectEnum<>(this, DirectOperation.values(), DirectOperation.AllocateWarrantRoute, this); 037 038 private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean = 039 new LogixNG_SelectNamedBean<>( 040 this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this); 041 042 private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct; 043 private String _dataReference = ""; 044 private String _dataLocalVariable = ""; 045 private String _dataFormula = ""; 046 private ExpressionNode _dataExpressionNode; 047 048 private String _trainData = ""; 049 private ControlAutoTrain _controlAutoTrain = ControlAutoTrain.Halt; 050 051 public ActionWarrant(String sys, String user) 052 throws BadUserNameException, BadSystemNameException { 053 super(sys, user); 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 ActionWarrant copy = new ActionWarrant(sysName, userName); 063 copy.setComment(getComment()); 064 _selectNamedBean.copy(copy._selectNamedBean); 065 _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean); 066 _selectEnum.copy(copy._selectEnum); 067 068 copy.setDataAddressing(_dataAddressing); 069 copy.setDataReference(_dataReference); 070 copy.setDataLocalVariable(_dataLocalVariable); 071 copy.setDataFormula(_dataFormula); 072 073 copy.setTrainData(_trainData); 074 copy.setControlAutoTrain(_controlAutoTrain); 075 076 return manager.registerAction(copy); 077 } 078 079 public LogixNG_SelectNamedBean<Warrant> getSelectNamedBean() { 080 return _selectNamedBean; 081 } 082 083 public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() { 084 return _selectMemoryNamedBean; 085 } 086 087 public LogixNG_SelectEnum<DirectOperation> getSelectEnum() { 088 return _selectEnum; 089 } 090 091 public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException { 092 _dataAddressing = addressing; 093 parseDataFormula(); 094 } 095 096 public NamedBeanAddressing getDataAddressing() { 097 return _dataAddressing; 098 } 099 100 public void setDataReference(@Nonnull String reference) { 101 if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) { 102 throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference"); 103 } 104 _dataReference = reference; 105 } 106 107 public String getDataReference() { 108 return _dataReference; 109 } 110 111 public void setDataLocalVariable(@Nonnull String localVariable) { 112 _dataLocalVariable = localVariable; 113 } 114 115 public String getDataLocalVariable() { 116 return _dataLocalVariable; 117 } 118 119 public void setDataFormula(@Nonnull String formula) throws ParserException { 120 _dataFormula = formula; 121 parseDataFormula(); 122 } 123 124 public String getDataFormula() { 125 return _dataFormula; 126 } 127 128 private void parseDataFormula() throws ParserException { 129 if (_dataAddressing == NamedBeanAddressing.Formula) { 130 Map<String, Variable> variables = new HashMap<>(); 131 132 RecursiveDescentParser parser = new RecursiveDescentParser(variables); 133 _dataExpressionNode = parser.parseExpression(_dataFormula); 134 } else { 135 _dataExpressionNode = null; 136 } 137 } 138 139 public void setTrainData(@Nonnull String trainData) { 140 _trainData = trainData; 141 } 142 143 public String getTrainData() { 144 return _trainData; 145 } 146 147 public void setControlAutoTrain(ControlAutoTrain controlAutoTrain) { 148 _controlAutoTrain = controlAutoTrain; 149 } 150 151 public ControlAutoTrain getControlAutoTrain() { 152 return _controlAutoTrain; 153 } 154 155 /** {@inheritDoc} */ 156 @Override 157 public Category getCategory() { 158 return Category.ITEM; 159 } 160 161 162 private String getNewData(DirectOperation theOper, SymbolTable symbolTable) 163 throws JmriException { 164 165 switch (_dataAddressing) { 166 case Direct: 167 switch(theOper) { 168 case SetTrainId: 169 case SetTrainName: 170 return _trainData; 171 case ControlAutoTrain: 172 return _controlAutoTrain.name(); 173 default: 174 return ""; 175 } 176 177 case Reference: 178 return ReferenceUtil.getReference(symbolTable, _dataReference); 179 180 case LocalVariable: 181 return TypeConversionUtil 182 .convertToString(symbolTable.getValue(_dataLocalVariable), false); 183 184 case Formula: 185 return _dataExpressionNode != null 186 ? TypeConversionUtil.convertToString( 187 _dataExpressionNode.calculate(symbolTable), false) 188 : null; 189 190 default: 191 throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name()); 192 } 193 } 194 195 196 /** {@inheritDoc} */ 197 @Override 198 public void execute() throws JmriException { 199 final ConditionalNG conditionalNG = getConditionalNG(); 200 Warrant warrant = _selectNamedBean.evaluateNamedBean(conditionalNG); 201 202 if (warrant == null) { 203 return; 204 } 205 206 SymbolTable symbolTable = conditionalNG.getSymbolTable(); 207 208 // Variables used in lambda must be effectively final 209 DirectOperation theOper = _selectEnum.evaluateEnum(conditionalNG); 210 211 if (!theOper.equals(DirectOperation.GetTrainLocation)) { 212 if (warrant.getRunMode() == Warrant.MODE_RUN && !theOper.equals(DirectOperation.ControlAutoTrain)) { 213 throw new JmriException("Cannot \"" + theOper.toString() + "\" when warrant is running - " + warrant.getDisplayName()); // NOI18N 214// log.info("Cannot \"{}\" when warrant is running - {}", theOper.toString(), warrant.getDisplayName()); 215// return; 216 } 217 } 218 219 ThreadingUtil.runOnLayoutWithJmriException(() -> { 220 String msg; 221 String err; 222 223 switch (theOper) { 224 case AllocateWarrantRoute: 225 warrant.allocateRoute(false, null); 226 break; 227 228 case DeallocateWarrant: 229 warrant.deAllocate(); 230 break; 231 232 case SetRouteTurnouts: 233 msg = warrant.setRoute(false, null); 234 if (msg != null) { 235 log.warn("Warrant {} unable to Set Route - {}", warrant.getDisplayName(), msg); // NOI18N 236 } 237 break; 238 239 case AutoRunTrain: 240 jmri.jmrit.logix.WarrantTableFrame frame = jmri.jmrit.logix.WarrantTableFrame.getDefault(); 241 err = frame.runTrain(warrant, Warrant.MODE_RUN); 242 if (err != null) { 243 warrant.stopWarrant(true, true); 244 throw new JmriException("runAutoTrain error - " + err); // NOI18N 245 } 246 break; 247 248 case ManuallyRunTrain: 249 err = warrant.setRoute(false, null); 250 if (err == null) { 251 err = warrant.setRunMode(Warrant.MODE_MANUAL, null, null, null, false); 252 } 253 if (err != null) { 254 throw new JmriException("runManualTrain error - " + err); // NOI18N 255 } 256 break; 257 258 case ControlAutoTrain: 259 int controlAction = 0; 260 switch (_controlAutoTrain) { 261 case Halt: 262 controlAction = Warrant.HALT; 263 break; 264 case Resume: 265 controlAction = Warrant.RESUME; 266 break; 267 case Stop: 268 controlAction = Warrant.STOP; 269 break; 270 case EStop: 271 controlAction = Warrant.ESTOP; 272 break; 273 case SpeedUp: 274 controlAction = Warrant.SPEED_UP; 275 break; 276 case MoveToNext: 277 controlAction = Warrant.RETRY_FWD; 278 break; 279 case Abort: 280 controlAction = Warrant.ABORT; 281 break; 282 default: 283 throw new IllegalArgumentException("invalid train control action: " + _controlAutoTrain); 284 } 285 if (!warrant.controlRunTrain(controlAction)) { 286 log.info("Warrant {} {}({}) failed. - {}", warrant.getDisplayName(), 287 theOper.toString(), _controlAutoTrain.toString(), warrant.getMessage()); 288 throw new JmriException("Warrant " + warrant.getDisplayName() + " " 289 + theOper.toString() +"(" + _controlAutoTrain.toString() + ") failed. " 290 + warrant.getMessage()); 291 } 292 break; 293 294 case SetTrainId: 295 if(!warrant.getSpeedUtil().setAddress(getNewData(theOper, symbolTable))) { 296 throw new JmriException("invalid train ID in action - " + warrant.getDisplayName()); // NOI18N 297 } 298 break; 299 300 case SetTrainName: 301 warrant.setTrainName(getNewData(theOper, symbolTable)); 302 break; 303 304 case GetTrainLocation: 305 Memory memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG); 306 if (memory != null) { 307 memory.setValue(warrant.getCurrentBlockName()); 308 } else { 309 throw new JmriException("Memory for GetTrainLocation is null for warrant - " + warrant.getDisplayName()); // NOI18N 310 } 311 break; 312 313 default: 314 throw new IllegalArgumentException("invalid oper state: " + theOper.name()); 315 } 316 }); 317 } 318 319 @Override 320 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 321 throw new UnsupportedOperationException("Not supported."); 322 } 323 324 @Override 325 public int getChildCount() { 326 return 0; 327 } 328 329 @Override 330 public String getShortDescription(Locale locale) { 331 return Bundle.getMessage(locale, "ActionWarrant_Short"); 332 } 333 334 @Override 335 public String getLongDescription(Locale locale) { 336 String namedBean = _selectNamedBean.getDescription(locale); 337 String state = _selectEnum.getDescription(locale); 338 String getLocationMemory = _selectMemoryNamedBean.getDescription(locale); 339 340 if (_selectEnum.getAddressing() == NamedBeanAddressing.Direct) { 341 if (_selectEnum.getEnum() != null) { 342 switch (_selectEnum.getEnum()) { 343 case SetTrainId: 344 return getLongDataDescription(locale, "ActionWarrant_Long_Train_Id", namedBean, _trainData); 345 case SetTrainName: 346 return getLongDataDescription(locale, "ActionWarrant_Long_Train_Name", namedBean, _trainData); 347 case ControlAutoTrain: 348 return getLongDataDescription(locale, "ActionWarrant_Long_Control", namedBean, _controlAutoTrain.name()); 349 case GetTrainLocation: 350 return getLongDataDescription(locale, "ActionWarrant_Long_Location", namedBean, getLocationMemory); 351 default: 352 // Fall thru and handle it in the end of the method 353 } 354 } 355 } 356 357 return Bundle.getMessage(locale, "ActionWarrant_Long", namedBean, state); 358 } 359 360 private String getLongDataDescription(Locale locale, String bundleKey, String namedBean, String value) { 361 switch (_dataAddressing) { 362 case Direct: 363 return Bundle.getMessage(locale, bundleKey, namedBean, value); 364 case Reference: 365 return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByReference", _dataReference)); 366 case LocalVariable: 367 return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable)); 368 case Formula: 369 return Bundle.getMessage(locale, bundleKey, namedBean, Bundle.getMessage("AddressByFormula", _dataFormula)); 370 default: 371 throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name()); 372 } 373 } 374 375 /** {@inheritDoc} */ 376 @Override 377 public void setup() { 378 // Do nothing 379 } 380 381 /** {@inheritDoc} */ 382 @Override 383 public void registerListenersForThisClass() { 384 _selectNamedBean.registerListeners(); 385 _selectEnum.registerListeners(); 386 _selectMemoryNamedBean.addPropertyChangeListener("value", this); 387 } 388 389 /** {@inheritDoc} */ 390 @Override 391 public void unregisterListenersForThisClass() { 392 _selectNamedBean.unregisterListeners(); 393 _selectEnum.unregisterListeners(); 394 _selectMemoryNamedBean.removePropertyChangeListener("value", this); 395 } 396 397 /** {@inheritDoc} */ 398 @Override 399 public void disposeMe() { 400 } 401 402 public enum DirectOperation { 403 None(""), 404 AllocateWarrantRoute(Bundle.getMessage("ActionWarrant_AllocateWarrantRoute")), 405 DeallocateWarrant(Bundle.getMessage("ActionWarrant_DeallocateWarrant")), 406 SetRouteTurnouts(Bundle.getMessage("ActionWarrant_SetRouteTurnouts")), 407 AutoRunTrain(Bundle.getMessage("ActionWarrant_AutoRunTrain")), 408 ManuallyRunTrain(Bundle.getMessage("ActionWarrant_ManuallyRunTrain")), 409 ControlAutoTrain(Bundle.getMessage("ActionWarrant_ControlAutoTrain")), 410 SetTrainId(Bundle.getMessage("ActionWarrant_SetTrainId")), 411 SetTrainName(Bundle.getMessage("ActionWarrant_SetTrainName")), 412 GetTrainLocation(Bundle.getMessage("ActionWarrant_GetTrainLocation")); 413 414 private final String _text; 415 416 private DirectOperation(String text) { 417 this._text = text; 418 } 419 420 @Override 421 public String toString() { 422 return _text; 423 } 424 425 } 426 427 public enum ControlAutoTrain { 428 Halt(Bundle.getMessage("ActionWarrant_Halt_AutoTrain")), 429 Resume(Bundle.getMessage("ActionWarrant_Resume_AutoTrain")), 430 Abort(Bundle.getMessage("ActionWarrant_Abort_AutoTrain")), 431 Stop(Bundle.getMessage("ActionWarrant_Stop_AutoTrain")), 432 EStop(Bundle.getMessage("ActionWarrant_EStop_AutoTrain")), 433 MoveToNext(Bundle.getMessage("ActionWarrant_MoveToNext_AutoTrain")), 434 SpeedUp(Bundle.getMessage("ActionWarrant_SpeedUp_AutoTrain")); 435 436 private final String _text; 437 438 private ControlAutoTrain(String text) { 439 this._text = text; 440 } 441 442 @Override 443 public String toString() { 444 return _text; 445 } 446 447 } 448 449 /** {@inheritDoc} */ 450 @Override 451 public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) { 452 _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 453 _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action); 454 } 455 456 /** {@inheritDoc} */ 457 @Override 458 public void propertyChange(PropertyChangeEvent evt) { 459 getConditionalNG().execute(); 460 } 461 462 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionWarrant.class); 463 464}