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