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     * Class for defining ActionListener for ACTION_DELAYED_SENSOR
1001     */
1002    class TimeSensor implements java.awt.event.ActionListener {
1003
1004        public TimeSensor(int index) {
1005            mIndex = index;
1006        }
1007
1008        private int mIndex = 0;
1009
1010        @Override
1011        public void actionPerformed(java.awt.event.ActionEvent event) {
1012            // set sensor state
1013            ConditionalAction action = _actionList.get(mIndex);
1014            //String devName = getDeviceName(action);
1015            //Sensor sn = InstanceManager.sensorManagerInstance().getSensor(devName);
1016            if (action.getNamedBean() == null) {
1017                log.error("{} Invalid delayed sensor name - {}", getDisplayName(), action.getDeviceName());  // NOI18N
1018            } else {
1019                // set the sensor
1020
1021                Sensor s = (Sensor) action.getNamedBean().getBean();
1022                try {
1023                    int act = action.getActionData();
1024                    if (act == Route.TOGGLE) {
1025                        // toggle from current state
1026                        int state = s.getKnownState();
1027                        if (state == Sensor.ACTIVE) {
1028                            act = Sensor.INACTIVE;
1029                        } else {
1030                            act = Sensor.ACTIVE;
1031                        }
1032                    }
1033                    s.setKnownState(act);
1034                } catch (JmriException e) {
1035                    log.warn("Exception setting delayed sensor {} in action", action.getDeviceName());  // NOI18N
1036                }
1037            }
1038            // Turn Timer OFF
1039            action.stopTimer();
1040        }
1041    }
1042
1043    /**
1044     * Class for defining ActionListener for ACTION_DELAYED_TURNOUT
1045     */
1046    class TimeTurnout implements java.awt.event.ActionListener {
1047
1048        public TimeTurnout(int index) {
1049            mIndex = index;
1050        }
1051
1052        private int mIndex = 0;
1053
1054        @Override
1055        public void actionPerformed(java.awt.event.ActionEvent event) {
1056            // set turnout state
1057            ConditionalAction action = _actionList.get(mIndex);
1058            if (action.getNamedBean() == null) {
1059                log.error("{} Invalid delayed turnout name - {}", getDisplayName(), action.getDeviceName());  // NOI18N
1060            } else {
1061                Turnout t = (Turnout) action.getNamedBean().getBean();
1062                int act = action.getActionData();
1063                if (act == Route.TOGGLE) {
1064                    // toggle from current state
1065                    int state = t.getKnownState();
1066                    if (state == Turnout.CLOSED) {
1067                        act = Turnout.THROWN;
1068                    } else {
1069                        act = Turnout.CLOSED;
1070                    }
1071                }
1072                // set the turnout
1073                t.setCommandedState(act);
1074            }
1075            // Turn Timer OFF
1076            action.stopTimer();
1077        }
1078    }
1079
1080    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultConditional.class);
1081}