001package jmri.implementation; 002 003import java.awt.GraphicsEnvironment; 004import java.beans.PropertyChangeEvent; 005import java.text.MessageFormat; 006import java.util.ArrayList; 007import java.util.BitSet; 008import java.util.List; 009 010import javax.annotation.CheckForNull; 011import javax.annotation.Nonnull; 012import javax.swing.*; 013 014import jmri.*; 015import jmri.jmrit.logix.OBlock; 016import jmri.jmrit.logix.Warrant; 017import jmri.util.ThreadingUtil; 018 019/** 020 * Class providing the basic logic of the Conditional interface. 021 * 022 * @author Dave Duchamp Copyright (C) 2007 023 * @author Pete Cressman Copyright (C) 2009, 2010, 2011 024 * @author Matthew Harris copyright (C) 2009 025 * @author Egbert Broerse i18n 2016 026 */ 027public class DefaultConditional extends AbstractNamedBean 028 implements Conditional { 029 030 static final java.util.ResourceBundle conditionalRbx = java.util.ResourceBundle 031 .getBundle("jmri.jmrit.conditional.ConditionalBundle"); 032 033 private final DefaultConditionalExecute conditionalExecute; 034 035 public DefaultConditional(String systemName, String userName) { 036 super(systemName, userName); 037 conditionalExecute = new DefaultConditionalExecute(this); 038 } 039 040 public DefaultConditional(String systemName) { 041 super(systemName); 042 conditionalExecute = new DefaultConditionalExecute(this); 043 } 044 045 @Override 046 public String getBeanType() { 047 return Bundle.getMessage("BeanNameConditional"); // NOI18N 048 } 049 050 // boolean expression of state variables 051 private String _antecedent = ""; 052 private Conditional.AntecedentOperator _logicType = 053 Conditional.AntecedentOperator.ALL_AND; 054 // variables (antecedent) parameters 055 private List<ConditionalVariable> _variableList = new ArrayList<>(); 056 // actions (consequent) parameters 057 protected List<ConditionalAction> _actionList = new ArrayList<>(); 058 059 private int _currentState = NamedBean.UNKNOWN; 060 private boolean _triggerActionsOnChange = true; 061 062 public static int getIndexInTable(int[] table, int entry) { 063 for (int i = 0; i < table.length; i++) { 064 if (entry == table[i]) { 065 return i; 066 } 067 } 068 return -1; 069 } 070 071 /** 072 * Get antecedent (boolean string expression) of Conditional. 073 */ 074 @Override 075 public String getAntecedentExpression() { 076 return _antecedent; 077 } 078 079 /** 080 * Get type of operators in the antecedent statement. 081 */ 082 @Override 083 public Conditional.AntecedentOperator getLogicType() { 084 return _logicType; 085 } 086 087 /** 088 * Set the logic type (all AND's all OR's or mixed AND's and OR's set the 089 * antecedent expression - should be a well formed boolean statement with 090 * parenthesis indicating the order of evaluation) 091 * 092 * @param type index of the logic type 093 * @param antecedent non-localized (US-english) string description of antecedent 094 */ 095 @Override 096 public void setLogicType(Conditional.AntecedentOperator type, String antecedent) { 097 _logicType = type; 098 _antecedent = antecedent; // non-localised (universal) string description 099 setState(NamedBean.UNKNOWN); 100 } 101 102 @Override 103 public boolean getTriggerOnChange() { 104 return _triggerActionsOnChange; 105 } 106 107 @Override 108 public void setTriggerOnChange(boolean trigger) { 109 _triggerActionsOnChange = trigger; 110 } 111 112 /** 113 * Set State Variables for this Conditional. Each state variable will 114 * evaluate either True or False when this Conditional is calculated. 115 * <p> 116 * This method assumes that all information has been validated. 117 */ 118 @Override 119 public void setStateVariables(@Nonnull List<ConditionalVariable> arrayList) { 120 log.debug("Conditional \"{}\" ({}) updated ConditionalVariable list.", 121 getUserName(), getSystemName()); // NOI18N 122 _variableList = arrayList; 123 } 124 125 /** 126 * Make deep clone of variables. 127 */ 128 @Override 129 @Nonnull 130 public List<ConditionalVariable> getCopyOfStateVariables() { 131 ArrayList<ConditionalVariable> variableList = new ArrayList<>(); 132 for (int i = 0; i < _variableList.size(); i++) { 133 ConditionalVariable variable = _variableList.get(i); 134 ConditionalVariable clone = new ConditionalVariable(); 135 clone.setNegation(variable.isNegated()); 136 clone.setOpern(variable.getOpern()); 137 clone.setType(variable.getType()); 138 clone.setName(variable.getName()); 139 clone.setDataString(variable.getDataString()); 140 clone.setNum1(variable.getNum1()); 141 clone.setNum2(variable.getNum2()); 142 clone.setTriggerActions(variable.doTriggerActions()); 143 clone.setState(variable.getState()); 144 clone.setGuiName(variable.getGuiName()); 145 variableList.add(clone); 146 } 147 return variableList; 148 } 149 150 /** 151 * Get the list of state variables for this Conditional. 152 * 153 * @return the list of state variables 154 */ 155 public List<ConditionalVariable> getStateVariableList() { 156 return _variableList; 157 } 158 159 /** 160 * Set list of actions. 161 */ 162 @Override 163 public void setAction(@Nonnull List<ConditionalAction> arrayList) { 164 _actionList = arrayList; 165 } 166 167 /** 168 * Make deep clone of actions. 169 */ 170 @Override 171 @Nonnull 172 public List<ConditionalAction> getCopyOfActions() { 173 ArrayList<ConditionalAction> actionList = new ArrayList<>(); 174 for (int i = 0; i < _actionList.size(); i++) { 175 ConditionalAction action = _actionList.get(i); 176 ConditionalAction clone = new DefaultConditionalAction(); 177 clone.setType(action.getType()); 178 clone.setOption(action.getOption()); 179 clone.setDeviceName(action.getDeviceName()); 180 clone.setActionData(action.getActionData()); 181 clone.setActionString(action.getActionString()); 182 actionList.add(clone); 183 } 184 return actionList; 185 } 186 187 /** 188 * Get the list of actions for this conditional. 189 * 190 * @return the list of actions 191 */ 192 @Nonnull 193 public List<ConditionalAction> getActionList() { 194 return _actionList; 195 } 196 197 /** 198 * Calculate this Conditional. 199 * When _enabled is false, Conditional.calculate will compute the state of 200 * the conditional, but will not trigger its actions. 201 * When _enabled is true, the state is computed and if the state 202 * has changed, will trigger all its actions. 203 * @param enable true to enable, else false. 204 */ 205 @Override 206 public int calculate(final boolean enable, PropertyChangeEvent evt) { 207 log.trace("calculate starts for {}", getSystemName()); // NOI18N 208 209 // check if there are no state variables 210 if (_variableList.isEmpty()) { 211 // if there are no state variables, no state can be calculated 212 setState(NamedBean.UNKNOWN); 213 return _currentState; 214 } 215 boolean result = true; 216 switch (_logicType) { 217 case ALL_AND: 218 for (int i = 0; (i < _variableList.size()) && result; i++) { 219 result = _variableList.get(i).evaluate(); 220 } 221 break; 222 case ALL_OR: 223 result = false; 224 for (int k = 0; (k < _variableList.size()) && !result; k++) { 225 result = _variableList.get(k).evaluate(); 226 } 227 break; 228 case MIXED: 229 char[] ch = _antecedent.toCharArray(); 230 int n = 0; 231 for (int j = 0; j < ch.length; j++) { 232 if (ch[j] != ' ') { 233 if (ch[j] == '{' || ch[j] == '[') { 234 ch[j] = '('; 235 } else if (ch[j] == '}' || ch[j] == ']') { 236 ch[j] = ')'; 237 } 238 ch[n++] = ch[j]; 239 } 240 } 241 try { 242 DataPair dp = parseCalculate(new String(ch, 0, n), _variableList); 243 result = dp.result; 244 } catch (NumberFormatException | IndexOutOfBoundsException | JmriException e) { 245 result = false; 246 log.error("{} parseCalculation error antecedent= {}, ex= {}", 247 getDisplayName(), _antecedent, e.toString(), e); 248 } 249 break; 250 default: 251 log.warn("Conditional {} fell through switch in calculate", getSystemName()); // NOI18N 252 break; 253 } 254 int newState = FALSE; 255 log.debug("Conditional \"{}\" ({}) has calculated its state to be {}. current state is {}. enabled= {}", 256 getUserName(), getSystemName(), result, _currentState, enable); // NOI18N 257 if (result) { 258 newState = TRUE; 259 } 260 261 boolean enabled = enable; 262 log.trace(" enabled starts at {}", enabled); 263 264 if (enabled && evt != null) { 265 // check if the current listener wants to (NOT) trigger actions 266 enabled = wantsToTrigger(evt); 267 log.trace(" wantsToTrigger sets enabled to {}", enabled); 268 } 269 if (_triggerActionsOnChange 270 // pre 1/15/2011 on change only behavior 271 && newState == _currentState) { 272 enabled = false; 273 log.trace(" _triggerActionsOnChange sets enabled to false"); 274 } 275 setState(newState); 276 if (enabled) { 277 takeActionIfNeeded(); 278 } 279 return _currentState; 280 } 281 282 /** 283 * Check if an event will trigger actions. 284 * 285 * @param evt the event that possibly triggers actions 286 * @return true if event will trigger actions; false otherwise 287 */ 288 boolean wantsToTrigger(PropertyChangeEvent evt) { 289 try { 290 String sysName = ((NamedBean) evt.getSource()).getSystemName(); 291 String userName = ((NamedBean) evt.getSource()).getUserName(); 292 for (int i = 0; i < _variableList.size(); i++) { 293 if (sysName.equals(_variableList.get(i).getName())) { 294 return _variableList.get(i).doTriggerActions(); 295 } 296 } 297 if (userName != null) { 298 for (int i = 0; i < _variableList.size(); i++) { 299 if (userName.equals(_variableList.get(i).getName())) { 300 return _variableList.get(i).doTriggerActions(); 301 } 302 } 303 } 304 } catch (ClassCastException e) { 305 log.error("{} PropertyChangeEvent source of unexpected type: {}", getDisplayName(), evt); // NOI18N 306 } 307 return true; 308 } 309 310 private static class DataPair { 311 boolean result = false; 312 int indexCount = 0; // index reached when parsing completed 313 BitSet argsUsed = null; // error detection for missing arguments 314 } 315 316 /** 317 * Check that an antecedent is well formed. 318 * 319 * @param ant the antecedent string description 320 * @param variableList arraylist of existing Conditional variables 321 * @return error message string if not well formed 322 */ 323 @Override 324 @CheckForNull 325 public String validateAntecedent(@Nonnull String ant, List<ConditionalVariable> variableList) { 326 char[] ch = ant.toCharArray(); 327 int n = 0; 328 for (int j = 0; j < ch.length; j++) { 329 if (ch[j] != ' ') { 330 if (ch[j] == '{' || ch[j] == '[') { 331 ch[j] = '('; 332 } else if (ch[j] == '}' || ch[j] == ']') { 333 ch[j] = ')'; 334 } 335 ch[n++] = ch[j]; 336 } 337 } 338 int count = 0; 339 for (int j = 0; j < n; j++) { 340 if (ch[j] == '(') { 341 count++; 342 } 343 if (ch[j] == ')') { 344 count--; 345 } 346 } 347 if (count > 0) { 348 return MessageFormat.format(conditionalRbx.getString("ParseError7"), ')'); 349 } 350 if (count < 0) { 351 return MessageFormat.format(conditionalRbx.getString("ParseError7"), '('); 352 } 353 try { 354 DataPair dp = parseCalculate(new String(ch, 0, n), variableList); 355 if (n != dp.indexCount) { 356 return MessageFormat.format(conditionalRbx.getString("ParseError4"), ch[dp.indexCount - 1]); 357 } 358 int index = dp.argsUsed.nextClearBit(0); 359 if (index >= 0 && index < variableList.size()) { 360 return MessageFormat.format(conditionalRbx.getString("ParseError5"), 361 variableList.size(), index + 1); 362 } 363 } catch (NumberFormatException | IndexOutOfBoundsException | JmriException nfe) { 364 return conditionalRbx.getString("ParseError6") + nfe.getMessage(); 365 } 366 return null; 367 } 368 369 /** 370 * Parses and computes one parenthesis level of a boolean statement. 371 * <p> 372 * Recursively calls inner parentheses levels. Note that all logic operators 373 * are detected by the parsing, therefore the internal negation of a 374 * variable is washed. 375 * 376 * @param expression The expression to be parsed 377 * @param variableList ConditionalVariables for R1, R2, etc 378 * @return a data pair consisting of the truth value of the level a count of 379 * the indices consumed to parse the level and a bitmap of the 380 * variable indices used. 381 * @throws jmri.JmriException if unable to compute the logic 382 */ 383 DataPair parseCalculate(String expression, List<ConditionalVariable> variableList) 384 throws JmriException { 385 386 // for simplicity, we force the string to upper case before scanning 387 String s = expression.toUpperCase(); 388 389 BitSet argsUsed = new BitSet(_variableList.size()); 390 DataPair dp; 391 boolean leftArg = false; 392 boolean rightArg = false; 393 int oper; 394 int k; 395 int i = 0; // index of String s 396 //int numArgs = 0; 397 if (s.charAt(i) == '(') { 398 dp = parseCalculate(s.substring(++i), variableList); 399 leftArg = dp.result; 400 i += dp.indexCount; 401 argsUsed.or(dp.argsUsed); 402 } else // cannot be '('. must be either leftArg or notleftArg 403 { 404 if (s.charAt(i) == 'R') { // NOI18N 405 try { 406 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 407 i += 2; 408 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 409 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 410 } 411 leftArg = variableList.get(k - 1).evaluate(); 412 if (variableList.get(k - 1).isNegated()) { 413 leftArg = !leftArg; 414 } 415 i++; 416 argsUsed.set(k - 1); 417 } else if ("NOT".equals(s.substring(i, i + 3))) { // NOI18N 418 i += 3; 419 420 // not leftArg 421 if (s.charAt(i) == '(') { 422 dp = parseCalculate(s.substring(++i), variableList); 423 leftArg = dp.result; 424 i += dp.indexCount; 425 argsUsed.or(dp.argsUsed); 426 } else if (s.charAt(i) == 'R') { // NOI18N 427 try { 428 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 429 i += 2; 430 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 431 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 432 } 433 leftArg = variableList.get(k - 1).evaluate(); 434 if (variableList.get(k - 1).isNegated()) { 435 leftArg = !leftArg; 436 } 437 i++; 438 argsUsed.set(k - 1); 439 } else { 440 throw new JmriException(MessageFormat.format( 441 conditionalRbx.getString("ParseError1"), s.substring(i))); 442 } 443 leftArg = !leftArg; 444 } else { 445 throw new JmriException(MessageFormat.format(conditionalRbx.getString("ParseError9"), s)); 446 } 447 } 448 // crank away to the right until a matching parent is reached 449 while (i < s.length()) { 450 if (s.charAt(i) != ')') { 451 // must be either AND or OR 452 if ("AND".equals(s.substring(i, i + 3))) { // NOI18N 453 i += 3; 454 oper = OPERATOR_AND; 455 } else if ("OR".equals(s.substring(i, i + 2))) { // NOI18N 456 i += 2; 457 oper = OPERATOR_OR; 458 } else { 459 throw new JmriException(MessageFormat.format( 460 conditionalRbx.getString("ParseError2"), s.substring(i))); 461 } 462 if (s.charAt(i) == '(') { 463 dp = parseCalculate(s.substring(++i), variableList); 464 rightArg = dp.result; 465 i += dp.indexCount; 466 argsUsed.or(dp.argsUsed); 467 } else // cannot be '('. must be either rightArg or notRightArg 468 { 469 if (s.charAt(i) == 'R') { // NOI18N 470 try { 471 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 472 i += 2; 473 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 474 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 475 } 476 rightArg = variableList.get(k - 1).evaluate(); 477 if (variableList.get(k - 1).isNegated()) { 478 rightArg = !rightArg; 479 } 480 i++; 481 argsUsed.set(k - 1); 482 } else if ("NOT".equals(s.substring(i, i + 3))) { // NOI18N 483 i += 3; 484 // not rightArg 485 if (s.charAt(i) == '(') { 486 dp = parseCalculate(s.substring(++i), variableList); 487 rightArg = dp.result; 488 i += dp.indexCount; 489 argsUsed.or(dp.argsUsed); 490 } else if (s.charAt(i) == 'R') { // NOI18N 491 try { 492 k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3))); 493 i += 2; 494 } catch (NumberFormatException | IndexOutOfBoundsException nfe) { 495 k = Integer.parseInt(String.valueOf(s.charAt(++i))); 496 } 497 rightArg = variableList.get(k - 1).evaluate(); 498 if (variableList.get(k - 1).isNegated()) { 499 rightArg = !rightArg; 500 } 501 i++; 502 argsUsed.set(k - 1); 503 } else { 504 throw new JmriException(MessageFormat.format( 505 conditionalRbx.getString("ParseError3"), s.substring(i))); 506 } 507 rightArg = !rightArg; 508 } else { 509 throw new JmriException(MessageFormat.format( 510 conditionalRbx.getString("ParseError9"), s.substring(i))); 511 } 512 } 513 if (oper == OPERATOR_AND) { 514 leftArg = (leftArg && rightArg); 515 } else if (oper == OPERATOR_OR) { 516 leftArg = (leftArg || rightArg); 517 } 518 } else { // This level done, pop recursion 519 i++; 520 break; 521 } 522 } 523 dp = new DataPair(); 524 dp.result = leftArg; 525 dp.indexCount = i; 526 dp.argsUsed = argsUsed; 527 return dp; 528 } 529 530 /** 531 * Compares action options, and takes action if appropriate 532 * <p> 533 * Only get here if a change in state has occurred when calculating this 534 * Conditional 535 */ 536 private void takeActionIfNeeded() { 537 log.trace("takeActionIfNeeded starts for {}", getSystemName()); 538 Reference<Integer> actionCount = new Reference<>(0); 539 int actionNeeded = 0; 540 List<String> errorList = new ArrayList<>(); 541 // Use a local copy of state to guarantee the entire list of actions will be fired off 542 // before a state change occurs that may block their completion. 543 int currentState = _currentState; 544 for (int i = 0; i < _actionList.size(); i++) { 545 ConditionalAction action = _actionList.get(i); 546 int neededAction = actionNeeded; 547 int option = action.getOption(); 548 log.trace(" takeActionIfNeeded considers action {} with currentState: {} and option: {}", 549 i, currentState, option); 550 551 if (((currentState == TRUE) && (option == ACTION_OPTION_ON_CHANGE_TO_TRUE)) 552 || ((currentState == FALSE) && (option == ACTION_OPTION_ON_CHANGE_TO_FALSE)) 553 || (option == ACTION_OPTION_ON_CHANGE)) { 554 // need to take this action 555 actionNeeded++; 556 NamedBean nb = null; 557 var anb = action.getNamedBean(); 558 if ( anb != null) { 559 nb = anb.getBean(); 560 } 561 Conditional.Action type = action.getType(); 562 String devName = getDeviceName(action); 563 if (devName == null) { 564 errorList.add("invalid memory name in action - " + action); // NOI18N 565 continue; 566 } 567 log.debug("getDeviceName()={} devName= {}", action.getDeviceName(), devName); 568 569 switch (type) { 570 case NONE: 571 break; 572 case SET_TURNOUT: 573 conditionalExecute.setTurnout(action, (Turnout) nb, actionCount, errorList); 574 break; 575 case RESET_DELAYED_TURNOUT: 576 conditionalExecute.delayedTurnout(action, actionCount, new TimeTurnout(i), true, devName); 577 break; 578 case DELAYED_TURNOUT: 579 conditionalExecute.delayedTurnout(action, actionCount, new TimeTurnout(i), false, devName); 580 break; 581 case CANCEL_TURNOUT_TIMERS: 582 conditionalExecute.cancelTurnoutTimers(action, actionCount, errorList, devName); 583 break; 584 case LOCK_TURNOUT: 585 conditionalExecute.lockTurnout(action, (Turnout) nb, actionCount, errorList); 586 break; 587 case SET_SIGNAL_APPEARANCE: 588 conditionalExecute.setSignalAppearance(action, (SignalHead) nb, actionCount, errorList); 589 break; 590 case SET_SIGNAL_HELD: 591 conditionalExecute.setSignalHeld(action, (SignalHead) nb, actionCount, errorList); 592 break; 593 case CLEAR_SIGNAL_HELD: 594 conditionalExecute.clearSignalHeld(action, (SignalHead) nb, actionCount, errorList); 595 break; 596 case SET_SIGNAL_DARK: 597 conditionalExecute.setSignalDark(action, (SignalHead) nb, actionCount, errorList); 598 break; 599 case SET_SIGNAL_LIT: 600 conditionalExecute.setSignalLit(action, (SignalHead) nb, actionCount, errorList); 601 break; 602 case TRIGGER_ROUTE: 603 conditionalExecute.triggerRoute(action, (Route) nb, actionCount, errorList); 604 break; 605 case SET_SENSOR: 606 conditionalExecute.setSensor(action, (Sensor) nb, actionCount, errorList, devName); 607 break; 608 case RESET_DELAYED_SENSOR: 609 conditionalExecute.delayedSensor(action, actionCount, 610 new TimeSensor(i), getMillisecondValue(action), true, devName); 611 break; 612 case DELAYED_SENSOR: 613 conditionalExecute.delayedSensor(action, actionCount, 614 new TimeSensor(i), getMillisecondValue(action), false, devName); 615 break; 616 case CANCEL_SENSOR_TIMERS: 617 conditionalExecute.cancelSensorTimers(action, actionCount, errorList, devName); 618 break; 619 case SET_LIGHT: 620 conditionalExecute.setLight(action, (Light) nb, actionCount, errorList); 621 break; 622 case SET_LIGHT_INTENSITY: 623 conditionalExecute.setLightIntensity(action, (Light) nb, 624 getIntegerValue(action), actionCount, errorList); 625 break; 626 case SET_LIGHT_TRANSITION_TIME: 627 conditionalExecute.setLightTransitionTime(action, (Light) nb, 628 getIntegerValue(action), actionCount, errorList); 629 break; 630 case SET_MEMORY: 631 conditionalExecute.setMemory(action, (Memory) nb, actionCount, errorList); 632 break; 633 case COPY_MEMORY: 634 conditionalExecute.copyMemory(action, (Memory) nb, getMemory(action.getActionString()), 635 getActionString(action), actionCount, errorList); 636 break; 637 case ENABLE_LOGIX: 638 conditionalExecute.enableLogix(action, actionCount, errorList, devName); 639 break; 640 case DISABLE_LOGIX: 641 conditionalExecute.disableLogix(action, actionCount, errorList, devName); 642 break; 643 case PLAY_SOUND: 644 conditionalExecute.playSound(action, getActionString(action), actionCount, errorList); 645 break; 646 case RUN_SCRIPT: 647 conditionalExecute.runScript(action, getActionString(action), actionCount); 648 break; 649 case SET_FAST_CLOCK_TIME: 650 conditionalExecute.setFastClockTime(action, actionCount); 651 break; 652 case START_FAST_CLOCK: 653 conditionalExecute.startFastClock(actionCount); 654 break; 655 case STOP_FAST_CLOCK: 656 conditionalExecute.stopFastClock(actionCount); 657 break; 658 case CONTROL_AUDIO: 659 conditionalExecute.controlAudio(action, devName); 660 break; 661 case JYTHON_COMMAND: 662 conditionalExecute.jythonCommand(action, getActionString(action), actionCount); 663 break; 664 case ALLOCATE_WARRANT_ROUTE: 665 conditionalExecute.allocateWarrantRoute(action, (Warrant) nb, actionCount, errorList); 666 break; 667 case DEALLOCATE_WARRANT_ROUTE: 668 conditionalExecute.deallocateWarrantRoute(action, (Warrant) nb, actionCount, errorList); 669 break; 670 case SET_ROUTE_TURNOUTS: 671 conditionalExecute.setRouteTurnouts(action, (Warrant) nb, actionCount, errorList); 672 break; 673 case GET_TRAIN_LOCATION: 674 conditionalExecute.getTrainLocation(action, (Warrant) nb, getMemory(action.getActionString()), 675 getActionString(action), actionCount, errorList); 676 break; 677 case SET_TRAIN_ID: 678 conditionalExecute.setTrainId(action, (Warrant) nb, 679 getActionString(action), actionCount, errorList); 680 break; 681 case SET_TRAIN_NAME: 682 conditionalExecute.setTrainName(action, (Warrant) nb, 683 getActionString(action), actionCount, errorList); 684 break; 685 case AUTO_RUN_WARRANT: 686 conditionalExecute.autoRunWarrant(action, (Warrant) nb, actionCount, errorList); 687 break; 688 case MANUAL_RUN_WARRANT: 689 conditionalExecute.manualRunWarrant(action, (Warrant) nb, actionCount, errorList); 690 break; 691 case CONTROL_TRAIN: 692 conditionalExecute.controlTrain(action, (Warrant) nb, actionCount, errorList, devName); 693 break; 694 case SET_SIGNALMAST_ASPECT: 695 conditionalExecute.setSignalMastAspect(action, (SignalMast) nb, 696 getActionString(action), actionCount, errorList); 697 break; 698 case SET_SIGNALMAST_HELD: 699 conditionalExecute.setSignalMastHeld(action, (SignalMast) nb, actionCount, errorList); 700 break; 701 case CLEAR_SIGNALMAST_HELD: 702 conditionalExecute.clearSignalMastHeld(action, (SignalMast) nb, actionCount, errorList); 703 break; 704 case SET_SIGNALMAST_DARK: 705 conditionalExecute.setSignalMastDark(action, (SignalMast) nb, actionCount, errorList); 706 break; 707 case SET_SIGNALMAST_LIT: 708 conditionalExecute.setSignalMastLit(action, (SignalMast) nb, actionCount, errorList); 709 break; 710 case SET_BLOCK_VALUE: 711 conditionalExecute.setBlockValue(action, (OBlock) nb, 712 getActionString(action), actionCount, errorList); 713 break; 714 case SET_BLOCK_ERROR: 715 conditionalExecute.setBlockError(action, (OBlock) nb, actionCount, errorList); 716 break; 717 case CLEAR_BLOCK_ERROR: 718 conditionalExecute.clearBlockError(action, (OBlock) nb, errorList); 719 break; 720 case DEALLOCATE_BLOCK: 721 conditionalExecute.deallocateBlock(action, (OBlock) nb, actionCount, errorList); 722 break; 723 case SET_BLOCK_OUT_OF_SERVICE: 724 conditionalExecute.setBlockOutOfService(action, (OBlock) nb, actionCount, errorList); 725 break; 726 case SET_BLOCK_IN_SERVICE: 727 conditionalExecute.setBlockInService(action, (OBlock) nb, actionCount, errorList); 728 break; 729 case GET_BLOCK_TRAIN_NAME: 730 conditionalExecute.getBlockTrainName(action, (OBlock) nb, getMemory(action.getActionString()), 731 getActionString(action), actionCount, errorList); 732 break; 733 case GET_BLOCK_WARRANT: 734 conditionalExecute.getBlockWarrant(action, (OBlock) nb, getMemory(action.getActionString()), 735 getActionString(action), actionCount, errorList); 736 break; 737 case SET_NXPAIR_ENABLED: 738 conditionalExecute.setNXPairEnabled(action, actionCount, errorList, devName); 739 break; 740 case SET_NXPAIR_DISABLED: 741 conditionalExecute.setNXPairDisabled(action, actionCount, errorList, devName); 742 break; 743 case SET_NXPAIR_SEGMENT: 744 conditionalExecute.setNXPairSegment(action, actionCount, errorList, devName); 745 break; 746 default: 747 log.warn("takeActionIfNeeded drops through switch statement for action {} of {}", 748 i, getSystemName()); 749 break; 750 } 751 } 752 if (log.isDebugEnabled()) { 753 log.debug("Global state= {} Local state= {} - Action {} taken for action = {} {} for device {}", 754 _currentState, currentState, actionNeeded > neededAction ? "WAS" : "NOT", 755 action.getTypeString(), action.getActionString(), action.getDeviceName()); 756 } 757 } 758 if (!errorList.isEmpty()) { 759 for (int i = 0; i < errorList.size(); i++) { 760 log.error(" error: {} - {}", getDisplayName(), errorList.get(i)); 761 } 762 if (!GraphicsEnvironment.isHeadless()) { 763 java.awt.Toolkit.getDefaultToolkit().beep(); 764 if (!skipErrorDialog) { 765 ThreadingUtil.runOnGUI( () -> 766 new ErrorDialog(errorList, this).setVisible(true)); 767 } 768 } 769 } 770 if (log.isDebugEnabled()) { 771 log.debug("Conditional \"{}\" ({} has {} actions and has executed {} actions of {} " 772 + "actions needed on state change to {}", 773 getUserName(), getSystemName(), _actionList.size(), actionCount, actionNeeded, currentState); // NOI18N 774 } 775 } // takeActionIfNeeded 776 777 private static volatile boolean skipErrorDialog = false; 778 779 private static synchronized void setSkipErrorDialog( boolean skip ) { 780 skipErrorDialog = skip; 781 } 782 783 private class ErrorDialog extends JDialog { 784 785 JCheckBox rememberSession; 786 787 ErrorDialog(List<String> list, Conditional cond) { 788 super(); 789 setTitle("Logix Runtime Errors"); // NOI18N 790 JPanel contentPanel = new JPanel(); 791 contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); 792 JPanel panel = new JPanel(); 793 panel.add(new JLabel("Errors occurred executing Actions in Conditional:")); // NOI18N 794 contentPanel.add(panel); 795 796 panel = new JPanel(); 797 panel.add(new JLabel(getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME))); 798 contentPanel.add(panel); 799 800 panel = new JPanel(); 801 panel.add(new JList<>(list.toArray(new String[0]))); 802 contentPanel.add(panel); 803 804 panel = new JPanel(); 805 rememberSession = new JCheckBox("Skip error dialog for this session only?"); // NOI18N 806 panel.add(rememberSession); 807 contentPanel.add(panel); 808 809 panel = new JPanel(); 810 JButton closeButton = new JButton("Close"); // NOI18N 811 closeButton.addActionListener( e -> { 812 DefaultConditional.setSkipErrorDialog(rememberSession.isSelected()); 813 this.dispose(); 814 }); 815 panel.add(closeButton); 816 contentPanel.add(panel); 817 setContentPane(contentPanel); 818 setLocation(250, 150); 819 pack(); 820 } 821 } 822 823 private String getDeviceName(@Nonnull ConditionalAction action) { 824 String devName = action.getDeviceName(); 825 if (devName != null && devName.length() > 0 && devName.charAt(0) == '@') { 826 String memName = devName.substring(1); 827 Memory m = getMemory(memName); 828 if (m == null) { 829 log.error("{} invalid memory name in action - {}", getDisplayName(), devName); // NOI18N 830 return null; 831 } 832 devName = (String) m.getValue(); 833 } 834 return devName; 835 } 836 837 private String getActionString(@Nonnull ConditionalAction action) { 838 String devAction = action.getActionString(); 839 if ( !devAction.isEmpty() && devAction.charAt(0) == '@') { 840 String memName = devAction.substring(1); 841 Memory m = getMemory(memName); 842 if (m == null) { 843 log.error("{} action \"{}\" has invalid memory name in actionString - {}", 844 getDisplayName(), action.getDeviceName(), action.getActionString()); 845 return ""; 846 } 847 devAction = (String) m.getValue(); 848 } 849 return devAction; 850 } 851 852 /** 853 * for backward compatibility with config files having system names in lower 854 * case 855 */ 856 @CheckForNull 857 private static Memory getMemory(String name) { 858 return InstanceManager.memoryManagerInstance().getMemory(name); 859 } 860 861 /** 862 * Get an integer from either a String literal or named memory reference. 863 * 864 * @param action an action containing either an integer or name of a Memory 865 * @return the integral value of the action or -1 if the action references a 866 * Memory that does not contain an integral value 867 */ 868 int getIntegerValue( @Nonnull ConditionalAction action) { 869 String sNumber = action.getActionString(); 870 int time; 871 try { 872 time = Integer.parseInt(sNumber); 873 } catch (NumberFormatException e) { 874 if (sNumber.charAt(0) == '@') { 875 sNumber = sNumber.substring(1); 876 } 877 Memory mem = getMemory(sNumber); 878 if (mem == null) { 879 log.error("invalid memory name for action time variable - {}, for Action \"{}\", " 880 + "in Conditional \"{}\" ({})", 881 sNumber, action.getTypeString(), getUserName(), getSystemName()); 882 return -1; 883 } 884 try { 885 time = Integer.parseInt((String) mem.getValue()); 886 } catch (NumberFormatException ex) { 887 log.error("invalid action number variable from memory, \"{}\" ({}), " 888 + "value = {}, for Action \"{}\", in Conditional \"{}\" ({})", 889 getUserName(), mem.getSystemName(), mem.getValue(), 890 action.getTypeString(), getUserName(), getSystemName()); 891 return -1; 892 } 893 } 894 return time; 895 } 896 897 /** 898 * Get the number of milliseconds from either a String literal or named 899 * memory reference containing a value representing a number of seconds. 900 * <p> 901 * The String is not I18N and should use a . decimal seperater. 902 * @param action an action containing either a number of seconds or name of 903 * a Memory 904 * @return the number of milliseconds represented by action of -1 if action 905 * references a Memory without a numeric value 906 */ 907 int getMillisecondValue(@Nonnull ConditionalAction action) { 908 String sNumber = action.getActionString(); 909 float time; 910 try { 911 time = Float.parseFloat(sNumber); 912 } catch (NumberFormatException e) { 913 if (sNumber.charAt(0) == '@') { 914 sNumber = sNumber.substring(1); 915 } 916 Memory mem = getMemory(sNumber); 917 if (mem == null) { 918 log.error("invalid memory name for action time variable - {}, " 919 + "for Action \"{}\", in Conditional \"{}\" ({})", 920 sNumber, action.getTypeString(), getUserName(), getSystemName()); 921 return -1; 922 } 923 try { 924 time = Float.parseFloat((String) mem.getValue()); 925 } catch (NumberFormatException ex) { 926 time = -1; 927 } 928 if (time <= 0) { 929 log.error("invalid Millisecond value from memory, \"{}\" ({}), " 930 + "value = {}, for Action \"{}\", in Conditional \"{}\" ({})", 931 getUserName(), mem.getSystemName(), mem.getValue(), 932 action.getTypeString(), getUserName(), getSystemName()); 933 } 934 } 935 return (int) (time * 1000); 936 } 937 938 /** 939 * Stop a sensor timer if one is actively delaying setting of the specified 940 * sensor 941 */ 942 @Override 943 public void cancelSensorTimer(String sname) { 944 for (int i = 0; i < _actionList.size(); i++) { 945 ConditionalAction action = _actionList.get(i); 946 if ((action.getType() == Conditional.Action.DELAYED_SENSOR) 947 || (action.getType() == Conditional.Action.RESET_DELAYED_SENSOR)) { 948 if (action.isTimerActive()) { 949 String devName = getDeviceName(action); 950 if ( devName == null ) { 951 log.error("When cancelling S Timer Could not locate Device Name for Sensor {}", sname); 952 return; 953 } 954 // have active set sensor timer - is it for our sensor? 955 if (devName.equals(sname)) { 956 // yes, names match, cancel timer 957 action.stopTimer(); 958 } else { 959 // check if same sensor by a different name 960 Sensor sn = InstanceManager.sensorManagerInstance().getSensor(devName); 961 if (sn == null) { 962 log.error("{} Unknown sensor *{} in cancelSensorTimer.", 963 getDisplayName(), action.getDeviceName()); 964 } else if (sname.equals(sn.getSystemName()) 965 || sname.equals(sn.getUserName())) { 966 // same sensor, cancel timer 967 action.stopTimer(); 968 } 969 } 970 } 971 } 972 } 973 } 974 975 /** 976 * Stop a turnout timer if one is actively delaying setting of the specified 977 * turnout 978 */ 979 @Override 980 public void cancelTurnoutTimer(String sname) { 981 for (int i = 0; i < _actionList.size(); i++) { 982 ConditionalAction action = _actionList.get(i); 983 if ((action.getType() == Conditional.Action.DELAYED_TURNOUT) 984 || (action.getType() == Conditional.Action.RESET_DELAYED_TURNOUT)) { 985 if (action.isTimerActive()) { 986 // have active set turnout timer - is it for our turnout? 987 String devName = getDeviceName(action); 988 if ( devName == null ) { 989 log.error("When cancelling T Timer Could not locate Device Name for Turnout {}", sname); 990 return; 991 } 992 if (devName.equals(sname)) { 993 // yes, names match, cancel timer 994 action.stopTimer(); 995 } else { 996 // check if same turnout by a different name 997 Turnout tn = InstanceManager.turnoutManagerInstance().getTurnout(devName); 998 if (tn == null) { 999 log.error("{} Unknown turnout *{} in cancelTurnoutTimer.", 1000 getDisplayName(), action.getDeviceName()); 1001 } else if (sname.equals(tn.getSystemName()) 1002 || sname.equals(tn.getUserName())) { 1003 // same turnout, cancel timer 1004 action.stopTimer(); 1005 } 1006 } 1007 } 1008 } 1009 } 1010 } 1011 1012 /** 1013 * State of the Conditional is returned. 1014 * 1015 * @return state value 1016 */ 1017 @Override 1018 public int getState() { 1019 return _currentState; 1020 } 1021 1022 /** 1023 * State of Conditional is set. Not really public for Conditionals. The 1024 * state of a Conditional is only changed by its calculate method, so the 1025 * state is really a read-only bound property. 1026 * 1027 * @param state the new state 1028 */ 1029 @Override 1030 public void setState(int state) { 1031 if (_currentState != state) { 1032 int oldState = _currentState; 1033 _currentState = state; 1034 firePropertyChange(NamedBean.PROPERTY_KNOWN_STATE, oldState, _currentState); 1035 } 1036 } 1037 1038 /** 1039 * Dispose this DefaultConditional. 1040 */ 1041 @Override 1042 public void dispose() { 1043 super.dispose(); 1044 for (int i = 0; i < _actionList.size(); i++) { 1045 _actionList.get(i).dispose(); 1046 } 1047 } 1048 1049 /** 1050 * Class for defining ActionListener for ACTION_DELAYED_SENSOR 1051 */ 1052 class TimeSensor implements java.awt.event.ActionListener { 1053 1054 TimeSensor(int index) { 1055 mIndex = index; 1056 } 1057 1058 private int mIndex = 0; 1059 1060 @Override 1061 public void actionPerformed(java.awt.event.ActionEvent event) { 1062 // set sensor state 1063 ConditionalAction action = _actionList.get(mIndex); 1064 if (action.getNamedBean() == null) { 1065 log.error("{} Invalid delayed sensor name - {}", getDisplayName(), action.getDeviceName()); 1066 } else { 1067 // set the sensor 1068 1069 var anb = action.getNamedBean(); 1070 if ( anb == null ) { 1071 log.error("No NamedBean for Acrion {}", action.getActionString()); 1072 return; 1073 } 1074 Sensor s = (Sensor) anb.getBean(); 1075 try { 1076 int act = action.getActionData(); 1077 if (act == Route.TOGGLE) { 1078 // toggle from current state 1079 int state = s.getKnownState(); 1080 if (state == Sensor.ACTIVE) { 1081 act = Sensor.INACTIVE; 1082 } else { 1083 act = Sensor.ACTIVE; 1084 } 1085 } 1086 s.setKnownState(act); 1087 } catch (JmriException e) { 1088 log.warn("Exception setting delayed sensor {} in action", action.getDeviceName()); // NOI18N 1089 } 1090 } 1091 // Turn Timer OFF 1092 action.stopTimer(); 1093 } 1094 } 1095 1096 /** 1097 * Class for defining ActionListener for ACTION_DELAYED_TURNOUT 1098 */ 1099 class TimeTurnout implements java.awt.event.ActionListener { 1100 1101 TimeTurnout(int index) { 1102 mIndex = index; 1103 } 1104 1105 private int mIndex = 0; 1106 1107 @Override 1108 public void actionPerformed(java.awt.event.ActionEvent event) { 1109 // set turnout state 1110 ConditionalAction action = _actionList.get(mIndex); 1111 var beanHandle = action.getNamedBean(); 1112 if (beanHandle == null) { 1113 log.error("{} Invalid delayed turnout name - {}", getDisplayName(), action.getDeviceName()); 1114 } else { 1115 Turnout t = (Turnout) beanHandle.getBean(); 1116 int act = action.getActionData(); 1117 if (act == Route.TOGGLE) { 1118 // toggle from current state 1119 int state = t.getKnownState(); 1120 if (state == Turnout.CLOSED) { 1121 act = Turnout.THROWN; 1122 } else { 1123 act = Turnout.CLOSED; 1124 } 1125 } 1126 // set the turnout 1127 t.setCommandedState(act); 1128 } 1129 // Turn Timer OFF 1130 action.stopTimer(); 1131 } 1132 } 1133 1134 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditional.class); 1135}