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