001package jmri.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.GraphicsEnvironment;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.beans.PropertyChangeEvent;
009import java.io.File;
010import java.util.ArrayList;
011import java.util.BitSet;
012import java.util.Date;
013import java.util.List;
014
015import javax.annotation.Nonnull;
016import javax.script.ScriptException;
017import javax.swing.BoxLayout;
018import javax.swing.JButton;
019import javax.swing.JCheckBox;
020import javax.swing.JDialog;
021import javax.swing.JLabel;
022import javax.swing.JList;
023import javax.swing.JPanel;
024import javax.swing.Timer;
025
026import jmri.*;
027import jmri.jmrit.Sound;
028import jmri.jmrit.audio.AudioListener;
029import jmri.jmrit.audio.AudioSource;
030import jmri.jmrit.entryexit.DestinationPoints;
031import jmri.jmrit.logix.OBlock;
032import jmri.jmrit.logix.Warrant;
033import jmri.script.JmriScriptEngineManager;
034import jmri.script.ScriptOutput;
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038
039/**
040 * Class providing the basic logic of the Conditional interface.
041 *
042 * @author Dave Duchamp Copyright (C) 2007
043 * @author Pete Cressman Copyright (C) 2009, 2010, 2011
044 * @author Matthew Harris copyright (C) 2009
045 * @author Egbert Broerse i18n 2016
046 */
047public class DefaultConditional extends AbstractNamedBean
048        implements Conditional {
049
050    static final java.util.ResourceBundle rbx = java.util.ResourceBundle.getBundle("jmri.jmrit.conditional.ConditionalBundle");  // NOI18N
051
052    public DefaultConditional(String systemName, String userName) {
053        super(systemName, userName);
054    }
055
056    public DefaultConditional(String systemName) {
057        super(systemName);
058    }
059
060    @Override
061    public String getBeanType() {
062        return Bundle.getMessage("BeanNameConditional");  // NOI18N
063    }
064
065    // boolean expression of state variables
066    private String _antecedent = "";
067    private Conditional.AntecedentOperator _logicType =
068            Conditional.AntecedentOperator.ALL_AND;
069    // variables (antecedent) parameters
070    private List<ConditionalVariable> _variableList = new ArrayList<>();
071    // actions (consequent) parameters
072    protected List<ConditionalAction> _actionList = new ArrayList<>();
073
074    private int _currentState = NamedBean.UNKNOWN;
075    private boolean _triggerActionsOnChange = true;
076
077    public static int getIndexInTable(int[] table, int entry) {
078        for (int i = 0; i < table.length; i++) {
079            if (entry == table[i]) {
080                return i;
081            }
082        }
083        return -1;
084    }
085
086    /**
087     * Get antecedent (boolean string expression) of Conditional.
088     */
089    @Override
090    public String getAntecedentExpression() {
091        return _antecedent;
092    }
093
094    /**
095     * Get type of operators in the antecedent statement.
096     */
097    @Override
098    public Conditional.AntecedentOperator getLogicType() {
099        return _logicType;
100    }
101
102    /**
103     * Set the logic type (all AND's all OR's or mixed AND's and OR's set the
104     * antecedent expression - should be a well formed boolean statement with
105     * parenthesis indicating the order of evaluation)
106     *
107     * @param type index of the logic type
108     * @param antecedent non-localized (US-english) string description of antecedent
109     */
110    @Override
111    public void setLogicType(Conditional.AntecedentOperator type, String antecedent) {
112        _logicType = type;
113        _antecedent = antecedent; // non-localised (universal) string description
114        setState(NamedBean.UNKNOWN);
115    }
116
117    @Override
118    public boolean getTriggerOnChange() {
119        return _triggerActionsOnChange;
120    }
121
122    @Override
123    public void setTriggerOnChange(boolean trigger) {
124        _triggerActionsOnChange = trigger;
125    }
126
127    /**
128     * Set State Variables for this Conditional. Each state variable will
129     * evaluate either True or False when this Conditional is calculated.
130     * <p>
131     * This method assumes that all information has been validated.
132     */
133    @Override
134    public void setStateVariables(List<ConditionalVariable> arrayList) {
135        log.debug("Conditional \"{}\" ({}) updated ConditionalVariable list.",
136                getUserName(), getSystemName());  // NOI18N
137        _variableList = arrayList;
138    }
139
140    /**
141     * Make deep clone of variables.
142     */
143    @Override
144    @Nonnull
145    public List<ConditionalVariable> getCopyOfStateVariables() {
146        ArrayList<ConditionalVariable> variableList = new ArrayList<>();
147        for (int i = 0; i < _variableList.size(); i++) {
148            ConditionalVariable variable = _variableList.get(i);
149            ConditionalVariable clone = new ConditionalVariable();
150            clone.setNegation(variable.isNegated());
151            clone.setOpern(variable.getOpern());
152            clone.setType(variable.getType());
153            clone.setName(variable.getName());
154            clone.setDataString(variable.getDataString());
155            clone.setNum1(variable.getNum1());
156            clone.setNum2(variable.getNum2());
157            clone.setTriggerActions(variable.doTriggerActions());
158            clone.setState(variable.getState());
159            clone.setGuiName(variable.getGuiName());
160            variableList.add(clone);
161        }
162        return variableList;
163    }
164
165    /**
166     * Get the list of state variables for this Conditional.
167     *
168     * @return the list of state variables
169     */
170    public List<ConditionalVariable> getStateVariableList() {
171        return _variableList;
172    }
173
174    /**
175     * Set list of actions.
176     */
177    @Override
178    public void setAction(List<ConditionalAction> arrayList) {
179        _actionList = arrayList;
180    }
181
182    /**
183     * Make deep clone of actions.
184     */
185    @Override
186    @Nonnull
187    public List<ConditionalAction> getCopyOfActions() {
188        ArrayList<ConditionalAction> actionList = new ArrayList<>();
189        for (int i = 0; i < _actionList.size(); i++) {
190            ConditionalAction action = _actionList.get(i);
191            ConditionalAction clone = new DefaultConditionalAction();
192            clone.setType(action.getType());
193            clone.setOption(action.getOption());
194            clone.setDeviceName(action.getDeviceName());
195            clone.setActionData(action.getActionData());
196            clone.setActionString(action.getActionString());
197            actionList.add(clone);
198        }
199        return actionList;
200    }
201
202    /**
203     * Get the list of actions for this conditional.
204     *
205     * @return the list of actions
206     */
207    public List<ConditionalAction> getActionList() {
208        return _actionList;
209    }
210
211    /**
212     * Calculate this Conditional. When _enabled is false, Conditional.calculate
213     * will compute the state of the conditional, but will not trigger its
214     * actions. When _enabled is true, the state is computed and if the state
215     * has changed, will trigger all its actions.
216     */
217    @Override
218    public int calculate(boolean enabled, PropertyChangeEvent evt) {
219        log.trace("calculate starts for {}", getSystemName());  // NOI18N
220
221        // check if  there are no state variables
222        if (_variableList.isEmpty()) {
223            // if there are no state variables, no state can be calculated
224            setState(NamedBean.UNKNOWN);
225            return _currentState;
226        }
227        boolean result = true;
228        switch (_logicType) {
229            case ALL_AND:
230                for (int i = 0; (i < _variableList.size()) && result; i++) {
231                    result = _variableList.get(i).evaluate();
232                }
233                break;
234            case ALL_OR:
235                result = false;
236                for (int k = 0; (k < _variableList.size()) && !result; k++) {
237                    result = _variableList.get(k).evaluate();
238                }
239                break;
240            case MIXED:
241                char[] ch = _antecedent.toCharArray();
242                int n = 0;
243                for (int j = 0; j < ch.length; j++) {
244                    if (ch[j] != ' ') {
245                        if (ch[j] == '{' || ch[j] == '[') {
246                            ch[j] = '(';
247                        } else if (ch[j] == '}' || ch[j] == ']') {
248                            ch[j] = ')';
249                        }
250                        ch[n++] = ch[j];
251                    }
252                }
253                try {
254                    DataPair dp = parseCalculate(new String(ch, 0, n), _variableList);
255                    result = dp.result;
256                } catch (NumberFormatException | IndexOutOfBoundsException | JmriException e) {
257                    result = false;
258                    log.error("{} parseCalculation error antecedent= {}, ex= {}", getDisplayName(), _antecedent, e,e);  // NOI18N
259                }
260                break;
261            default:
262                log.warn("Conditional {} fell through switch in calculate", getSystemName());  // NOI18N
263                break;
264        }
265        int newState = FALSE;
266        log.debug("Conditional \"{}\" ({}) has calculated its state to be {}. current state is {}. enabled= {}",
267                getUserName(), getSystemName(), result, _currentState, enabled);  // NOI18N
268        if (result) {
269            newState = TRUE;
270        }
271
272        log.trace("   enabled starts at {}", enabled);  // NOI18N
273
274        if (enabled) {
275            if (evt != null) {
276                // check if the current listener wants to (NOT) trigger actions
277                enabled = wantsToTrigger(evt);
278                log.trace("   wantsToTrigger sets enabled to {}", enabled);  // NOI18N
279            }
280        }
281        if (_triggerActionsOnChange) {
282            // pre 1/15/2011 on change only behavior
283            if (newState == _currentState) {
284                enabled = false;
285                log.trace("   _triggerActionsOnChange sets enabled to false");  // NOI18N
286            }
287        }
288        setState(newState);
289        if (enabled) {
290            takeActionIfNeeded();
291        }
292        return _currentState;
293    }
294
295    /**
296     * Check if an event will trigger actions.
297     *
298     * @param evt the event that possibly triggers actions
299     * @return true if event will trigger actions; false otherwise
300     */
301    boolean wantsToTrigger(PropertyChangeEvent evt) {
302        try {
303            String sysName = ((NamedBean) evt.getSource()).getSystemName();
304            String userName = ((NamedBean) evt.getSource()).getUserName();
305            for (int i = 0; i < _variableList.size(); i++) {
306                if (sysName.equals(_variableList.get(i).getName())) {
307                    return _variableList.get(i).doTriggerActions();
308                }
309            }
310            if (userName != null) {
311                for (int i = 0; i < _variableList.size(); i++) {
312                    if (userName.equals(_variableList.get(i).getName())) {
313                        return _variableList.get(i).doTriggerActions();
314                    }
315                }
316            }
317        } catch (ClassCastException e) {
318            log.error("{} PropertyChangeEvent source of unexpected type: {}", getDisplayName(), evt);  // NOI18N
319        }
320        return true;
321    }
322
323    static class DataPair {
324        boolean result = false;
325        int indexCount = 0;         // index reached when parsing completed
326        BitSet argsUsed = null;     // error detection for missing arguments
327    }
328
329    /**
330     * Check that an antecedent is well formed.
331     *
332     * @param ant the antecedent string description
333     * @param variableList arraylist of existing Conditional variables
334     * @return error message string if not well formed
335     */
336    @Override
337    public String validateAntecedent(String ant, List<ConditionalVariable> variableList) {
338        char[] ch = ant.toCharArray();
339        int n = 0;
340        for (int j = 0; j < ch.length; j++) {
341            if (ch[j] != ' ') {
342                if (ch[j] == '{' || ch[j] == '[') {
343                    ch[j] = '(';
344                } else if (ch[j] == '}' || ch[j] == ']') {
345                    ch[j] = ')';
346                }
347                ch[n++] = ch[j];
348            }
349        }
350        int count = 0;
351        for (int j = 0; j < n; j++) {
352            if (ch[j] == '(') {
353                count++;
354            }
355            if (ch[j] == ')') {
356                count--;
357            }
358        }
359        if (count > 0) {
360            return java.text.MessageFormat.format(
361                    rbx.getString("ParseError7"), new Object[]{')'});  // NOI18N
362        }
363        if (count < 0) {
364            return java.text.MessageFormat.format(
365                    rbx.getString("ParseError7"), new Object[]{'('});  // NOI18N
366        }
367        try {
368            DataPair dp = parseCalculate(new String(ch, 0, n), variableList);
369            if (n != dp.indexCount) {
370                return java.text.MessageFormat.format(
371                        rbx.getString("ParseError4"), new Object[]{ch[dp.indexCount - 1]});  // NOI18N
372            }
373            int index = dp.argsUsed.nextClearBit(0);
374            if (index >= 0 && index < variableList.size()) {
375                return java.text.MessageFormat.format(
376                        rbx.getString("ParseError5"),  // NOI18N
377                        new Object[]{variableList.size(), index + 1});
378            }
379        } catch (NumberFormatException | IndexOutOfBoundsException | JmriException nfe) {
380            return rbx.getString("ParseError6") + nfe.getMessage();  // NOI18N
381        }
382        return null;
383    }
384
385    /**
386     * Parses and computes one parenthesis level of a boolean statement.
387     * <p>
388     * Recursively calls inner parentheses levels. Note that all logic operators
389     * are detected by the parsing, therefore the internal negation of a
390     * variable is washed.
391     *
392     * @param s            The expression to be parsed
393     * @param variableList ConditionalVariables for R1, R2, etc
394     * @return a data pair consisting of the truth value of the level a count of
395     *         the indices consumed to parse the level and a bitmap of the
396     *         variable indices used.
397     * @throws jmri.JmriException if unable to compute the logic
398     */
399    DataPair parseCalculate(String s, List<ConditionalVariable> variableList)
400            throws JmriException {
401
402        // for simplicity, we force the string to upper case before scanning
403        s = s.toUpperCase();
404
405        BitSet argsUsed = new BitSet(_variableList.size());
406        DataPair dp = null;
407        boolean leftArg = false;
408        boolean rightArg = false;
409        int oper = OPERATOR_NONE;
410        int k = -1;
411        int i = 0;      // index of String s
412        //int numArgs = 0;
413        if (s.charAt(i) == '(') {
414            dp = parseCalculate(s.substring(++i), variableList);
415            leftArg = dp.result;
416            i += dp.indexCount;
417            argsUsed.or(dp.argsUsed);
418        } else // cannot be '('.  must be either leftArg or notleftArg
419        {
420            if (s.charAt(i) == 'R') {  // NOI18N
421                try {
422                    k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3)));
423                    i += 2;
424                } catch (NumberFormatException | IndexOutOfBoundsException nfe) {
425                    k = Integer.parseInt(String.valueOf(s.charAt(++i)));
426                }
427                leftArg = variableList.get(k - 1).evaluate();
428                if (variableList.get(k - 1).isNegated()) {
429                    leftArg = !leftArg;
430                }
431                i++;
432                argsUsed.set(k - 1);
433            } else if ("NOT".equals(s.substring(i, i + 3))) {  // NOI18N
434                i += 3;
435
436                // not leftArg
437                if (s.charAt(i) == '(') {
438                    dp = parseCalculate(s.substring(++i), variableList);
439                    leftArg = dp.result;
440                    i += dp.indexCount;
441                    argsUsed.or(dp.argsUsed);
442                } else if (s.charAt(i) == 'R') {  // NOI18N
443                    try {
444                        k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3)));
445                        i += 2;
446                    } catch (NumberFormatException | IndexOutOfBoundsException nfe) {
447                        k = Integer.parseInt(String.valueOf(s.charAt(++i)));
448                    }
449                    leftArg = variableList.get(k - 1).evaluate();
450                    if (variableList.get(k - 1).isNegated()) {
451                        leftArg = !leftArg;
452                    }
453                    i++;
454                    argsUsed.set(k - 1);
455                } else {
456                    throw new JmriException(java.text.MessageFormat.format(
457                            rbx.getString("ParseError1"), new Object[]{s.substring(i)}));  // NOI18N
458                }
459                leftArg = !leftArg;
460            } else {
461                throw new JmriException(java.text.MessageFormat.format(
462                        rbx.getString("ParseError9"), new Object[]{s}));  // NOI18N
463            }
464        }
465        // crank away to the right until a matching parent is reached
466        while (i < s.length()) {
467            if (s.charAt(i) != ')') {
468                // must be either AND or OR
469                if ("AND".equals(s.substring(i, i + 3))) {  // NOI18N
470                    i += 3;
471                    oper = OPERATOR_AND;
472                } else if ("OR".equals(s.substring(i, i + 2))) {  // NOI18N
473                    i += 2;
474                    oper = OPERATOR_OR;
475                } else {
476                    throw new JmriException(java.text.MessageFormat.format(
477                            rbx.getString("ParseError2"), new Object[]{s.substring(i)}));  // NOI18N
478                }
479                if (s.charAt(i) == '(') {
480                    dp = parseCalculate(s.substring(++i), variableList);
481                    rightArg = dp.result;
482                    i += dp.indexCount;
483                    argsUsed.or(dp.argsUsed);
484                } else // cannot be '('.  must be either rightArg or notRightArg
485                {
486                    if (s.charAt(i) == 'R') {  // NOI18N
487                        try {
488                            k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3)));
489                            i += 2;
490                        } catch (NumberFormatException | IndexOutOfBoundsException nfe) {
491                            k = Integer.parseInt(String.valueOf(s.charAt(++i)));
492                        }
493                        rightArg = variableList.get(k - 1).evaluate();
494                        if (variableList.get(k - 1).isNegated()) {
495                            rightArg = !rightArg;
496                        }
497                        i++;
498                        argsUsed.set(k - 1);
499                    } else if ("NOT".equals(s.substring(i, i + 3))) {  // NOI18N
500                        i += 3;
501                        // not rightArg
502                        if (s.charAt(i) == '(') {
503                            dp = parseCalculate(s.substring(++i), variableList);
504                            rightArg = dp.result;
505                            i += dp.indexCount;
506                            argsUsed.or(dp.argsUsed);
507                        } else if (s.charAt(i) == 'R') {  // NOI18N
508                            try {
509                                k = Integer.parseInt(String.valueOf(s.substring(i + 1, i + 3)));
510                                i += 2;
511                            } catch (NumberFormatException | IndexOutOfBoundsException nfe) {
512                                k = Integer.parseInt(String.valueOf(s.charAt(++i)));
513                            }
514                            rightArg = variableList.get(k - 1).evaluate();
515                            if (variableList.get(k - 1).isNegated()) {
516                                rightArg = !rightArg;
517                            }
518                            i++;
519                            argsUsed.set(k - 1);
520                        } else {
521                            throw new JmriException(java.text.MessageFormat.format(
522                                    rbx.getString("ParseError3"), new Object[]{s.substring(i)}));  // NOI18N
523                        }
524                        rightArg = !rightArg;
525                    } else {
526                        throw new JmriException(java.text.MessageFormat.format(
527                                rbx.getString("ParseError9"), new Object[]{s.substring(i)}));  // NOI18N
528                    }
529                }
530                if (oper == OPERATOR_AND) {
531                    leftArg = (leftArg && rightArg);
532                } else if (oper == OPERATOR_OR) {
533                    leftArg = (leftArg || rightArg);
534                }
535            } else {  // This level done, pop recursion
536                i++;
537                break;
538            }
539        }
540        dp = new DataPair();
541        dp.result = leftArg;
542        dp.indexCount = i;
543        dp.argsUsed = argsUsed;
544        return dp;
545    }
546
547    /**
548     * Compares action options, and takes action if appropriate
549     * <p>
550     * Only get here if a change in state has occurred when calculating this
551     * Conditional
552     */
553    @SuppressWarnings({"deprecation", "fallthrough"})
554    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH")
555    // it's unfortunate that this is such a huge method, because these annotations
556    // have to apply to more than 500 lines of code - jake
557    private void takeActionIfNeeded() {
558        if (log.isTraceEnabled()) {
559            log.trace("takeActionIfNeeded starts for {}", getSystemName());  // NOI18N
560        }
561        int actionCount = 0;
562        int actionNeeded = 0;
563        int act = 0;
564        int state = 0;
565        ArrayList<String> errorList = new ArrayList<>();
566        // Use a local copy of state to guarantee the entire list of actions will be fired off
567        // before a state change occurs that may block their completion.
568        int currentState = _currentState;
569        for (int i = 0; i < _actionList.size(); i++) {
570            ConditionalAction action = _actionList.get(i);
571            int neededAction = actionNeeded;
572            int option = action.getOption();
573            if (log.isTraceEnabled()) {
574                log.trace(" takeActionIfNeeded considers action {} with currentState: {} and option: {}", i, currentState, option);  // NOI18N
575            }
576            if (((currentState == TRUE) && (option == ACTION_OPTION_ON_CHANGE_TO_TRUE))
577                    || ((currentState == FALSE) && (option == ACTION_OPTION_ON_CHANGE_TO_FALSE))
578                    || (option == ACTION_OPTION_ON_CHANGE)) {
579                // need to take this action
580                actionNeeded++;
581                SignalHead h = null;
582                SignalMast f = null;
583                Logix x = null;
584                Light lgt = null;
585                Warrant w = null;
586                NamedBean nb = null;
587                if (action.getNamedBean() != null) {
588                    nb = action.getNamedBean().getBean();
589                }
590                int value = 0;
591                Timer timer = null;
592                Conditional.Action type = action.getType();
593                String devName = getDeviceName(action);
594                if (devName == null) {
595                    errorList.add("invalid memory name in action - " + action);  // NOI18N
596                    continue;
597                }
598                if (log.isDebugEnabled()) {
599                    log.debug("getDeviceName()={} devName= {}", action.getDeviceName(), devName);  // NOI18N
600                }
601                switch (type) {
602                    case NONE:
603                        break;
604                    case SET_TURNOUT:
605                        Turnout t = (Turnout) nb;
606                        if (t == null) {
607                            errorList.add("invalid turnout name in action - " + action.getDeviceName());  // NOI18N
608                        } else {
609                            act = action.getActionData();
610                            if (act == Route.TOGGLE) {
611                                state = t.getKnownState();
612                                if (state == Turnout.CLOSED) {
613                                    act = Turnout.THROWN;
614                                } else {
615                                    act = Turnout.CLOSED;
616                                }
617                            }
618                            t.setCommandedState(act);
619                            actionCount++;
620                        }
621                        break;
622                    case RESET_DELAYED_TURNOUT:
623                        action.stopTimer();
624                    // fall through
625                    case DELAYED_TURNOUT:
626                        if (!action.isTimerActive()) {
627                            // Create a timer if one does not exist
628                            timer = action.getTimer();
629                            if (timer == null) {
630                                action.setListener(new TimeTurnout(i));
631                                timer = new Timer(2000, action.getListener());
632                                timer.setRepeats(true);
633                            }
634                            // Start the Timer to set the turnout
635                            value = getMillisecondValue(action);
636                            if (value < 0) {
637                                break;
638                            }
639                            timer.setInitialDelay(value);
640                            action.setTimer(timer);
641                            action.startTimer();
642                            actionCount++;
643                        } else {
644                            log.warn("timer already active on request to start delayed turnout action - {}", devName);
645                        }
646                        break;
647                    case CANCEL_TURNOUT_TIMERS:
648                        ConditionalManager cmg = jmri.InstanceManager.getDefault(jmri.ConditionalManager.class);
649                        java.util.Iterator<Conditional> iter = cmg.getNamedBeanSet().iterator();
650                        while (iter.hasNext()) {
651                            String sname = iter.next().getSystemName();
652                            
653                            Conditional c = cmg.getBySystemName(sname);
654                            if (c == null) {
655                                errorList.add("Conditional null during cancel turnout timers for "  // NOI18N
656                                        + action.getDeviceName());
657                                continue; // no more processing of this one
658                            }
659                            
660                            c.cancelTurnoutTimer(devName);
661                            actionCount++;
662                        }
663                        break;
664                    case LOCK_TURNOUT:
665                        Turnout tl = (Turnout) nb;
666                        if (tl == null) {
667                            errorList.add("invalid turnout name in action - " + action.getDeviceName());  // NOI18N
668                        } else {
669                            act = action.getActionData();
670                            if (act == Route.TOGGLE) {
671                                if (tl.getLocked(Turnout.CABLOCKOUT)) {
672                                    act = Turnout.UNLOCKED;
673                                } else {
674                                    act = Turnout.LOCKED;
675                                }
676                            }
677                            if (act == Turnout.LOCKED) {
678                                tl.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true);
679                            } else if (act == Turnout.UNLOCKED) {
680                                tl.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, false);
681                            }
682                            actionCount++;
683                        }
684                        break;
685                    case SET_SIGNAL_APPEARANCE:
686                        h = (SignalHead) nb;
687                        if (h == null) {
688                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
689                        } else {
690                            h.setAppearance(action.getActionData());
691                            actionCount++;
692                        }
693                        break;
694                    case SET_SIGNAL_HELD:
695                        h = (SignalHead) nb;
696                        if (h == null) {
697                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
698                        } else {
699                            h.setHeld(true);
700                            actionCount++;
701                        }
702                        break;
703                    case CLEAR_SIGNAL_HELD:
704                        h = (SignalHead) nb;
705                        if (h == null) {
706                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
707                        } else {
708                            h.setHeld(false);
709                            actionCount++;
710                        }
711                        break;
712                    case SET_SIGNAL_DARK:
713                        h = (SignalHead) nb;
714                        if (h == null) {
715                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
716                        } else {
717                            h.setLit(false);
718                            actionCount++;
719                        }
720                        break;
721                    case SET_SIGNAL_LIT:
722                        h = (SignalHead) nb;
723                        if (h == null) {
724                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
725                        } else {
726                            h.setLit(true);
727                            actionCount++;
728                        }
729                        break;
730                    case TRIGGER_ROUTE:
731                        Route r = (Route) nb;
732                        if (r == null) {
733                            errorList.add("invalid Route name in action - " + action.getDeviceName());  // NOI18N
734                        } else {
735                            r.setRoute();
736                            actionCount++;
737                        }
738                        break;
739                    case SET_SENSOR:
740                        Sensor sn = (Sensor) nb;
741                        if (sn == null) {
742                            errorList.add("invalid Sensor name in action - " + action.getDeviceName());  // NOI18N
743                        } else {
744                            act = action.getActionData();
745                            if (act == Route.TOGGLE) {
746                                state = sn.getState();
747                                if (state == Sensor.ACTIVE) {
748                                    act = Sensor.INACTIVE;
749                                } else {
750                                    act = Sensor.ACTIVE;
751                                }
752                            }
753                            try {
754                                sn.setKnownState(act);
755                                actionCount++;
756                            } catch (JmriException e) {
757                                log.warn("Exception setting Sensor {} in action", devName);  // NOI18N
758                            }
759                        }
760                        break;
761                    case RESET_DELAYED_SENSOR:
762                        action.stopTimer();
763                    // fall through
764                    case DELAYED_SENSOR:
765                        if (!action.isTimerActive()) {
766                            // Create a timer if one does not exist
767                            timer = action.getTimer();
768                            if (timer == null) {
769                                action.setListener(new TimeSensor(i));
770                                timer = new Timer(2000, action.getListener());
771                                timer.setRepeats(true);
772                            }
773                            // Start the Timer to set the turnout
774                            value = getMillisecondValue(action);
775                            if (value < 0) {
776                                break;
777                            }
778                            timer.setInitialDelay(value);
779                            action.setTimer(timer);
780                            action.startTimer();
781                            actionCount++;
782                        } else {
783                            log.warn("timer already active on request to start delayed sensor action - {}", devName);
784                        }
785                        break;
786                    case CANCEL_SENSOR_TIMERS:
787                        ConditionalManager cm = jmri.InstanceManager.getDefault(jmri.ConditionalManager.class);
788                        java.util.Iterator<Conditional> itr = cm.getNamedBeanSet().iterator();
789                        while (itr.hasNext()) {
790                            String sname = itr.next().getSystemName();
791                            Conditional c = cm.getBySystemName(sname);
792                            if (c == null) {
793                                errorList.add("Conditional null during cancel sensor timers for "  // NOI18N
794                                        + action.getDeviceName());
795                                continue; // no more processing of this one
796                            }
797                            
798                            c.cancelSensorTimer(devName);
799                            actionCount++;
800                        }
801                        break;
802                    case SET_LIGHT:
803                        lgt = (Light) nb;
804                        if (lgt == null) {
805                            errorList.add("invalid light name in action - " + action.getDeviceName());  // NOI18N
806                        } else {
807                            act = action.getActionData();
808                            if (act == Route.TOGGLE) {
809                                state = lgt.getState();
810                                if (state == Light.ON) {
811                                    act = Light.OFF;
812                                } else {
813                                    act = Light.ON;
814                                }
815                            }
816                            lgt.setState(act);
817                            actionCount++;
818                        }
819                        break;
820                    case SET_LIGHT_INTENSITY:
821                        lgt = (Light) nb;
822                        if (lgt == null) {
823                            errorList.add("invalid light name in action - " + action.getDeviceName());  // NOI18N
824                        } else {
825                            try {
826                                value = getIntegerValue(action);
827                                if (value < 0) {
828                                    break;
829                                }
830                                if (lgt instanceof VariableLight) {
831                                    ((VariableLight)lgt).setTargetIntensity((value) / 100.0);
832                                } else {
833                                    lgt.setState(value > 0.5 ? Light.ON : Light.OFF);
834                                }
835                                actionCount++;
836                            } catch (IllegalArgumentException e) {
837                                errorList.add("Exception in set light intensity action - " + action.getDeviceName());  // NOI18N
838                            }
839                        }
840                        break;
841                    case SET_LIGHT_TRANSITION_TIME:
842                        lgt = (Light) nb;
843                        if (lgt == null) {
844                            errorList.add("invalid light name in action - " + action.getDeviceName());  // NOI18N
845                        } else {
846                            try {
847                                value = getIntegerValue(action);
848                                if (value < 0) {
849                                    break;
850                                }
851                                if (lgt instanceof VariableLight) {
852                                    ((VariableLight)lgt).setTransitionTime(value);
853                                }
854                                actionCount++;
855                            } catch (IllegalArgumentException e) {
856                                errorList.add("Exception in set light transition time action - " + action.getDeviceName());  // NOI18N
857                            }
858                        }
859                        break;
860                    case SET_MEMORY:
861                        Memory m = (Memory) nb;
862                        if (m == null) {
863                            errorList.add("invalid memory name in action - " + action.getDeviceName());  // NOI18N
864                        } else {
865                            m.setValue(action.getActionString());
866                            actionCount++;
867                        }
868                        break;
869                    case COPY_MEMORY:
870                        Memory mFrom = (Memory) nb;
871                        if (mFrom == null) {
872                            errorList.add("invalid memory name in action - " + action.getDeviceName());  // NOI18N
873                        } else {
874                            Memory mTo = getMemory(action.getActionString());
875                            if (mTo == null) {
876                                errorList.add("invalid memory name in action - " + action.getActionString());  // NOI18N
877                            } else {
878                                mTo.setValue(mFrom.getValue());
879                                actionCount++;
880                            }
881                        }
882                        break;
883                    case ENABLE_LOGIX:
884                        x = InstanceManager.getDefault(jmri.LogixManager.class).getLogix(devName);
885                        if (x == null) {
886                            errorList.add("invalid logix name in action - " + action.getDeviceName());  // NOI18N
887                        } else {
888                            x.setEnabled(true);
889                            actionCount++;
890                        }
891                        break;
892                    case DISABLE_LOGIX:
893                        x = InstanceManager.getDefault(jmri.LogixManager.class).getLogix(devName);
894                        if (x == null) {
895                            errorList.add("invalid logix name in action - " + action.getDeviceName());  // NOI18N
896                        } else {
897                            x.setEnabled(false);
898                            actionCount++;
899                        }
900                        break;
901                    case PLAY_SOUND:
902                        String path = getActionString(action);
903                        if (!path.equals("")) {
904                            Sound sound = action.getSound();
905                            if (sound == null) {
906                                try {
907                                    sound = new Sound(path);
908                                } catch (NullPointerException ex) {
909                                    errorList.add("invalid path to sound: " + path);  // NOI18N
910                                }
911                            }
912                            if (sound != null) {
913                                sound.play();
914                            }
915                            actionCount++;
916                        }
917                        break;
918                    case RUN_SCRIPT:
919                        if (!(getActionString(action).equals(""))) {
920                            JmriScriptEngineManager.getDefault().runScript(new File(jmri.util.FileUtil.getExternalFilename(getActionString(action))));
921                            actionCount++;
922                        }
923                        break;
924                    case SET_FAST_CLOCK_TIME:
925                        Date date = InstanceManager.getDefault(jmri.Timebase.class).getTime();
926                        date.setHours(action.getActionData() / 60);
927                        date.setMinutes(action.getActionData() - ((action.getActionData() / 60) * 60));
928                        date.setSeconds(0);
929                        InstanceManager.getDefault(jmri.Timebase.class).userSetTime(date);
930                        actionCount++;
931                        break;
932                    case START_FAST_CLOCK:
933                        InstanceManager.getDefault(jmri.Timebase.class).setRun(true);
934                        actionCount++;
935                        break;
936                    case STOP_FAST_CLOCK:
937                        InstanceManager.getDefault(jmri.Timebase.class).setRun(false);
938                        actionCount++;
939                        break;
940                    case CONTROL_AUDIO:
941                        Audio audio = InstanceManager.getDefault(jmri.AudioManager.class).getAudio(devName);
942                        if (audio == null) {
943                            break;
944                        }
945                        if (audio.getSubType() == Audio.SOURCE) {
946                            AudioSource audioSource = (AudioSource) audio;
947                            switch (action.getActionData()) {
948                                case Audio.CMD_PLAY:
949                                    audioSource.play();
950                                    break;
951                                case Audio.CMD_STOP:
952                                    audioSource.stop();
953                                    break;
954                                case Audio.CMD_PLAY_TOGGLE:
955                                    audioSource.togglePlay();
956                                    break;
957                                case Audio.CMD_PAUSE:
958                                    audioSource.pause();
959                                    break;
960                                case Audio.CMD_RESUME:
961                                    audioSource.resume();
962                                    break;
963                                case Audio.CMD_PAUSE_TOGGLE:
964                                    audioSource.togglePause();
965                                    break;
966                                case Audio.CMD_REWIND:
967                                    audioSource.rewind();
968                                    break;
969                                case Audio.CMD_FADE_IN:
970                                    audioSource.fadeIn();
971                                    break;
972                                case Audio.CMD_FADE_OUT:
973                                    audioSource.fadeOut();
974                                    break;
975                                case Audio.CMD_RESET_POSITION:
976                                    audioSource.resetCurrentPosition();
977                                    break;
978                                default:
979                                    break;
980                            }
981                        } else if (audio.getSubType() == Audio.LISTENER) {
982                            AudioListener audioListener = (AudioListener) audio;
983                            switch (action.getActionData()) {
984                                case Audio.CMD_RESET_POSITION:
985                                    audioListener.resetCurrentPosition();
986                                    break;
987                                default:
988                                    break; // nothing needed for others
989                            }
990                        }
991                        break;
992                    case JYTHON_COMMAND:
993                        if (!(getActionString(action).isEmpty())) {
994                            // add the text to the output frame
995                            ScriptOutput.writeScript(getActionString(action));
996                            // and execute
997                            
998                            javax.script.ScriptEngine se =  JmriScriptEngineManager.getDefault().getEngine(JmriScriptEngineManager.PYTHON);
999                            if (se!=null) {
1000                                try {
1001                                    JmriScriptEngineManager.getDefault().eval(getActionString(action), se);
1002                                } catch (ScriptException ex) {
1003                                    log.error("Error executing script:", ex);  // NOI18N
1004                                }
1005                            } else {
1006                                log.error("Error getting default ScriptEngine");
1007                            }
1008                            actionCount++;
1009                        }
1010                        break;
1011                    case ALLOCATE_WARRANT_ROUTE:
1012                        w = (Warrant) nb;
1013                        if (w == null) {
1014                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1015                        } else {
1016                            String msg = w.allocateRoute(false, null);
1017                            if (msg != null) {
1018                                log.info("Warrant {} - {}", action.getDeviceName(), msg);  // NOI18N
1019                            }
1020                            actionCount++;
1021                        }
1022                        break;
1023                    case DEALLOCATE_WARRANT_ROUTE:
1024                        w = (Warrant) nb;
1025                        if (w == null) {
1026                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1027                        } else {
1028                            w.deAllocate();
1029                            actionCount++;
1030                        }
1031                        break;
1032                    case SET_ROUTE_TURNOUTS:
1033                        w = (Warrant) nb;
1034                        if (w == null) {
1035                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1036                        } else {
1037                            String msg = w.setRoute(false, null);
1038                            if (msg != null) {
1039                                log.info("Warrant {} unable to Set Route - {}", action.getDeviceName(), msg);  // NOI18N
1040                            }
1041                            actionCount++;
1042                        }
1043                        break;
1044                    case THROTTLE_FACTOR:
1045                        log.info("Set warrant Throttle Factor deprecated - Use Warrrant Preferences");  // NOI18N
1046                        break;
1047                    case SET_TRAIN_ID:
1048                        w = (Warrant) nb;
1049                        if (w == null) {
1050                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1051                        } else {
1052                            if(!w.getSpeedUtil().setAddress(getActionString(action))) {
1053                                errorList.add("invalid train ID in action - " + action.getDeviceName());  // NOI18N
1054                            }
1055                            actionCount++;
1056                        }
1057                        break;
1058                    case SET_TRAIN_NAME:
1059                        w = (Warrant) nb;
1060                        if (w == null) {
1061                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1062                        } else {
1063                            w.setTrainName(getActionString(action));
1064                            actionCount++;
1065                        }
1066                        break;
1067                    case AUTO_RUN_WARRANT:
1068                        w = (Warrant) nb;
1069                        if (w == null) {
1070                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1071                        } else {
1072                            jmri.jmrit.logix.WarrantTableFrame frame = jmri.jmrit.logix.WarrantTableFrame.getDefault();
1073                            String err = frame.runTrain(w, Warrant.MODE_RUN);
1074                            if (err != null) {
1075                                errorList.add("runAutoTrain error - " + err);  // NOI18N
1076                                w.stopWarrant(true, true);
1077                            }
1078                            actionCount++;
1079                        }
1080                        break;
1081                    case MANUAL_RUN_WARRANT:
1082                        w = (Warrant) nb;
1083                        if (w == null) {
1084                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1085                        } else {
1086                            String err = w.setRoute(false, null);
1087                            if (err == null) {
1088                                err = w.setRunMode(Warrant.MODE_MANUAL, null, null, null, false);
1089                            }
1090                            if (err != null) {
1091                                errorList.add("runManualTrain error - " + err);  // NOI18N
1092                            }
1093                            actionCount++;
1094                        }
1095                        break;
1096                    case CONTROL_TRAIN:
1097                        w = (Warrant) nb;
1098                        if (w == null) {
1099                            errorList.add("invalid Warrant name in action - " + action.getDeviceName());  // NOI18N
1100                        } else {
1101                            if (!w.controlRunTrain(action.getActionData())) {
1102                                log.info("Train {} not running  - {}", w.getSpeedUtil().getRosterId(), devName);  // NOI18N
1103                            }
1104                            actionCount++;
1105                        }
1106                        break;
1107                    case SET_SIGNALMAST_ASPECT:
1108                        f = (SignalMast) nb;
1109                        if (f == null) {
1110                            errorList.add("invalid Signal Mast name in action - " + action.getDeviceName());  // NOI18N
1111                        } else {
1112                            f.setAspect(getActionString(action));
1113                            actionCount++;
1114                        }
1115                        break;
1116                    case SET_SIGNALMAST_HELD:
1117                        f = (SignalMast) nb;
1118                        if (f == null) {
1119                            errorList.add("invalid Signal Mast name in action - " + action.getDeviceName());  // NOI18N
1120                        } else {
1121                            f.setHeld(true);
1122                            actionCount++;
1123                        }
1124                        break;
1125                    case CLEAR_SIGNALMAST_HELD:
1126                        f = (SignalMast) nb;
1127                        if (f == null) {
1128                            errorList.add("invalid Signal Mast name in action - " + action.getDeviceName());  // NOI18N
1129                        } else {
1130                            f.setHeld(false);
1131                            actionCount++;
1132                        }
1133                        break;
1134                    case SET_SIGNALMAST_DARK:
1135                        f = (SignalMast) nb;
1136                        if (f == null) {
1137                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
1138                        } else {
1139                            f.setLit(false);
1140                            actionCount++;
1141                        }
1142                        break;
1143                    case SET_SIGNALMAST_LIT:
1144                        f = (SignalMast) nb;
1145                        if (f == null) {
1146                            errorList.add("invalid Signal Head name in action - " + action.getDeviceName());  // NOI18N
1147                        } else {
1148                            f.setLit(true);
1149                            actionCount++;
1150                        }
1151                        break;
1152                    case SET_BLOCK_VALUE:
1153                        OBlock b = (OBlock) nb;
1154                        if (b == null) {
1155                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1156                        } else {
1157                            b.setValue(getActionString(action));
1158                            actionCount++;
1159                        }
1160                        break;
1161                    case SET_BLOCK_ERROR:
1162                        b = (OBlock) nb;
1163                        if (b == null) {
1164                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1165                        } else {
1166                            b.setError(true);
1167                            actionCount++;
1168                        }
1169                        break;
1170                    case CLEAR_BLOCK_ERROR:
1171                        b = (OBlock) nb;
1172                        if (b == null) {
1173                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1174                        } else {
1175                            b.setError(false);
1176                        }
1177                        break;
1178                    case DEALLOCATE_BLOCK:
1179                        b = (OBlock) nb;
1180                        if (b == null) {
1181                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1182                        } else {
1183                            b.deAllocate(null);
1184                            actionCount++;
1185                        }
1186                        break;
1187                    case SET_BLOCK_OUT_OF_SERVICE:
1188                        b = (OBlock) nb;
1189                        if (b == null) {
1190                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1191                        } else {
1192                            b.setOutOfService(true);
1193                            actionCount++;
1194                        }
1195                        break;
1196                    case SET_BLOCK_IN_SERVICE:
1197                        b = (OBlock) nb;
1198                        if (b == null) {
1199                            errorList.add("invalid Block name in action - " + action.getDeviceName());  // NOI18N
1200                        } else {
1201                            b.setOutOfService(false);
1202                            actionCount++;
1203                        }
1204                        break;
1205                    case SET_NXPAIR_ENABLED:
1206                        DestinationPoints dp = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(devName);
1207                        if (dp == null) {
1208                            errorList.add("Invalid NX Pair name in action - " + action.getDeviceName());  // NOI18N
1209                        } else {
1210                            dp.setEnabled(true);
1211                            actionCount++;
1212                        }
1213                        break;
1214                    case SET_NXPAIR_DISABLED:
1215                        dp = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(devName);
1216                        if (dp == null) {
1217                            errorList.add("Invalid NX Pair name in action - " + action.getDeviceName());  // NOI18N
1218                        } else {
1219                            dp.setEnabled(false);
1220                            actionCount++;
1221                        }
1222                        break;
1223                    case SET_NXPAIR_SEGMENT:
1224                        dp = jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(devName);
1225                        if (dp == null) {
1226                            errorList.add("Invalid NX Pair name in action - " + action.getDeviceName());  // NOI18N
1227                        } else {
1228                            jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).
1229                                    setSingleSegmentRoute(devName);
1230                            actionCount++;
1231                        }
1232                        break;
1233                    default:
1234                        log.warn("takeActionIfNeeded drops through switch statement for action {} of {}", i, getSystemName());  // NOI18N
1235                        break;
1236                }
1237            }
1238            if (log.isDebugEnabled()) {
1239                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
1240            }
1241        }
1242        if (errorList.size() > 0) {
1243            for (int i = 0; i < errorList.size(); i++) {
1244                log.error("{} - {}", getDisplayName(), errorList.get(i));
1245            }
1246            if (!GraphicsEnvironment.isHeadless()) {
1247                java.awt.Toolkit.getDefaultToolkit().beep();
1248                if (!_skipErrorDialog) {
1249                    new ErrorDialog(errorList, this);
1250                }
1251            }
1252        }
1253        if (log.isDebugEnabled()) {
1254            log.debug("Conditional \"{}\" ({} has {} actions and has executed {} actions of {} actions needed on state change to {}", getUserName(), getSystemName(), _actionList.size(), actionCount, actionNeeded, currentState);  // NOI18N
1255        }
1256    }   // takeActionIfNeeded
1257
1258    static private boolean _skipErrorDialog = false;
1259
1260    class ErrorDialog extends JDialog {
1261
1262        JCheckBox rememberSession;
1263
1264        ErrorDialog(List<String> list, Conditional cond) {
1265            super();
1266            setTitle("Logix Runtime Errors");  // NOI18N
1267            JPanel contentPanel = new JPanel();
1268            contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
1269            JPanel panel = new JPanel();
1270            panel.add(new JLabel("Errors occurred executing Actions in Conditional:"));  // NOI18N
1271            contentPanel.add(panel);
1272
1273            panel = new JPanel();
1274            panel.add(new JLabel(getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME)));
1275            contentPanel.add(panel);
1276
1277            panel = new JPanel();
1278            panel.add(new JList<>(list.toArray(new String[0])));
1279            contentPanel.add(panel);
1280
1281            panel = new JPanel();
1282            rememberSession = new JCheckBox("Skip error dialog for this session only?");  // NOI18N
1283            panel.add(rememberSession);
1284            contentPanel.add(panel);
1285
1286            panel = new JPanel();
1287            JButton closeButton = new JButton("Close");  // NOI18N
1288            closeButton.addActionListener(new ActionListener() {
1289                @Override
1290                public void actionPerformed(ActionEvent a) {
1291                    if (rememberSession.isSelected()) {
1292                        _skipErrorDialog = true;
1293                    }
1294                    dispose();
1295                }
1296            });
1297            panel.add(closeButton);
1298            contentPanel.add(panel);
1299            setContentPane(contentPanel);
1300            setLocation(250, 150);
1301            pack();
1302            setVisible(true);
1303        }
1304    }
1305
1306    private String getDeviceName(ConditionalAction action) {
1307        String devName = action.getDeviceName();
1308        if (devName != null && devName.length() > 0 && devName.charAt(0) == '@') {
1309            String memName = devName.substring(1);
1310            Memory m = getMemory(memName);
1311            if (m == null) {
1312                log.error("{} invalid memory name in action - {}", getDisplayName(), devName);  // NOI18N
1313                return null;
1314            }
1315            devName = (String) m.getValue();
1316        }
1317        return devName;
1318    }
1319
1320    private String getActionString(ConditionalAction action) {
1321        String devAction = action.getActionString();
1322        if (devAction != null && devAction.length() > 0 && devAction.charAt(0) == '@') {
1323            String memName = devAction.substring(1);
1324            Memory m = getMemory(memName);
1325            if (m == null) {
1326                log.error("{} action \"{}\" has invalid memory name in actionString - {}", getDisplayName(), action.getDeviceName(), action.getActionString());  // NOI18N
1327                return "";
1328            }
1329            devAction = (String) m.getValue();
1330        }
1331        return devAction;
1332    }
1333
1334    /**
1335     * for backward compatibility with config files having system names in lower
1336     * case
1337     */
1338    static private Memory getMemory(String name) {
1339        return InstanceManager.memoryManagerInstance().getMemory(name);
1340    }
1341
1342    /**
1343     * Get an integer from either a String literal or named memory reference.
1344     *
1345     * @param action an action containing either an integer or name of a Memory
1346     * @return the integral value of the action or -1 if the action references a
1347     *         Memory that does not contain an integral value
1348     */
1349    int getIntegerValue(ConditionalAction action) {
1350        String sNumber = action.getActionString();
1351        int time = 0;
1352        try {
1353            time = Integer.parseInt(sNumber);
1354        } catch (NumberFormatException e) {
1355            if (sNumber.charAt(0) == '@') {
1356                sNumber = sNumber.substring(1);
1357            }
1358            Memory mem = getMemory(sNumber);
1359            if (mem == null) {
1360                log.error("invalid memory name for action time variable - {}, for Action \"{}\", in Conditional \"{}\" ({})", sNumber, action.getTypeString(), getUserName(), getSystemName());  // NOI18N
1361                return -1;
1362            }
1363            try {
1364                time = Integer.parseInt((String) mem.getValue());
1365            } catch (NumberFormatException ex) {
1366                log.error("invalid action number variable from memory, \"{}\" ({}), value = {}, for Action \"{}\", in Conditional \"{}\" ({})", getUserName(), mem.getSystemName(), mem.getValue(), action.getTypeString(), getUserName(), getSystemName());  // NOI18N
1367                return -1;
1368            }
1369        }
1370        return time;
1371    }
1372
1373    /**
1374     * Get the number of milliseconds from either a String literal or named
1375     * memory reference containing a value representing a number of seconds.
1376     *
1377     * @param action an action containing either a number of seconds or name of
1378     *               a Memory
1379     * @return the number of milliseconds represented by action of -1 if action
1380     *         references a Memory without a numeric value
1381     */
1382    int getMillisecondValue(ConditionalAction action) {
1383        String sNumber = action.getActionString();
1384        float time = 0;
1385        try {
1386            time = Float.parseFloat(sNumber);
1387        } catch (NumberFormatException e) {
1388            if (sNumber.charAt(0) == '@') {
1389                sNumber = sNumber.substring(1);
1390            }
1391            Memory mem = getMemory(sNumber);
1392            if (mem == null) {
1393                log.error("invalid memory name for action time variable - {}, for Action \"{}\", in Conditional \"{}\" ({})", sNumber, action.getTypeString(), getUserName(), getSystemName());  // NOI18N
1394                return -1;
1395            }
1396            try {
1397                time = Float.parseFloat((String) mem.getValue());
1398            } catch (NumberFormatException ex) {
1399                time = -1;
1400            }
1401            if (time <= 0) {
1402                log.error("invalid Millisecond value from memory, \"{}\" ({}), value = {}, for Action \"{}\", in Conditional \"{}\" ({})", getUserName(), mem.getSystemName(), mem.getValue(), action.getTypeString(), getUserName(), getSystemName());  // NOI18N
1403            }
1404        }
1405        return (int) (time * 1000);
1406    }
1407
1408    /**
1409     * Stop a sensor timer if one is actively delaying setting of the specified
1410     * sensor
1411     */
1412    @Override
1413    public void cancelSensorTimer(String sname) {
1414        for (int i = 0; i < _actionList.size(); i++) {
1415            ConditionalAction action = _actionList.get(i);
1416            if ((action.getType() == Conditional.Action.DELAYED_SENSOR)
1417                    || (action.getType() == Conditional.Action.RESET_DELAYED_SENSOR)) {
1418                if (action.isTimerActive()) {
1419                    String devName = getDeviceName(action);
1420                    // have active set sensor timer - is it for our sensor?
1421                    if (devName.equals(sname)) {
1422                        // yes, names match, cancel timer
1423                        action.stopTimer();
1424                    } else {
1425                        // check if same sensor by a different name
1426                        Sensor sn = InstanceManager.sensorManagerInstance().getSensor(devName);
1427                        if (sn == null) {
1428                            log.error("{} Unknown sensor *{} in cancelSensorTimer.", getDisplayName(), action.getDeviceName());  // NOI18N
1429                        } else if (sname.equals(sn.getSystemName())
1430                                || sname.equals(sn.getUserName())) {
1431                            // same sensor, cancel timer
1432                            action.stopTimer();
1433                        }
1434                    }
1435                }
1436            }
1437        }
1438    }
1439
1440    /**
1441     * Stop a turnout timer if one is actively delaying setting of the specified
1442     * turnout
1443     */
1444    @Override
1445    public void cancelTurnoutTimer(String sname) {
1446        for (int i = 0; i < _actionList.size(); i++) {
1447            ConditionalAction action = _actionList.get(i);
1448            if ((action.getType() == Conditional.Action.DELAYED_TURNOUT)
1449                    || (action.getType() == Conditional.Action.RESET_DELAYED_TURNOUT)) {
1450                if (action.isTimerActive()) {
1451                    // have active set turnout timer - is it for our turnout?
1452                    String devName = getDeviceName(action);
1453                    if (devName.equals(sname)) {
1454                        // yes, names match, cancel timer
1455                        action.stopTimer();
1456                    } else {
1457                        // check if same turnout by a different name
1458                        Turnout tn = InstanceManager.turnoutManagerInstance().getTurnout(devName);
1459                        if (tn == null) {
1460                            log.error("{} Unknown turnout *{} in cancelTurnoutTimer.", getDisplayName(), action.getDeviceName());  // NOI18N
1461                        } else if (sname.equals(tn.getSystemName())
1462                                || sname.equals(tn.getUserName())) {
1463                            // same turnout, cancel timer
1464                            action.stopTimer();
1465                        }
1466                    }
1467                }
1468            }
1469        }
1470    }
1471
1472    /**
1473     * State of the Conditional is returned.
1474     *
1475     * @return state value
1476     */
1477    @Override
1478    public int getState() {
1479        return _currentState;
1480    }
1481
1482    /**
1483     * State of Conditional is set. Not really public for Conditionals. The
1484     * state of a Conditional is only changed by its calculate method, so the
1485     * state is really a read-only bound property.
1486     *
1487     * @param state the new state
1488     */
1489    @Override
1490    public void setState(int state) {
1491        if (_currentState != state) {
1492            int oldState = _currentState;
1493            _currentState = state;
1494            firePropertyChange("KnownState", oldState, _currentState);  // NOI18N
1495        }
1496    }
1497
1498    /**
1499     * Class for defining ActionListener for ACTION_DELAYED_SENSOR
1500     */
1501    class TimeSensor implements java.awt.event.ActionListener {
1502
1503        public TimeSensor(int index) {
1504            mIndex = index;
1505        }
1506
1507        private int mIndex = 0;
1508
1509        @Override
1510        public void actionPerformed(java.awt.event.ActionEvent event) {
1511            // set sensor state
1512            ConditionalAction action = _actionList.get(mIndex);
1513            //String devName = getDeviceName(action);
1514            //Sensor sn = InstanceManager.sensorManagerInstance().getSensor(devName);
1515            if (action.getNamedBean() == null) {
1516                log.error("{} Invalid delayed sensor name - {}", getDisplayName(), action.getDeviceName());  // NOI18N
1517            } else {
1518                // set the sensor
1519
1520                Sensor s = (Sensor) action.getNamedBean().getBean();
1521                try {
1522                    int act = action.getActionData();
1523                    if (act == Route.TOGGLE) {
1524                        // toggle from current state
1525                        int state = s.getKnownState();
1526                        if (state == Sensor.ACTIVE) {
1527                            act = Sensor.INACTIVE;
1528                        } else {
1529                            act = Sensor.ACTIVE;
1530                        }
1531                    }
1532                    s.setKnownState(act);
1533                } catch (JmriException e) {
1534                    log.warn("Exception setting delayed sensor {} in action", action.getDeviceName());  // NOI18N
1535                }
1536            }
1537            // Turn Timer OFF
1538            action.stopTimer();
1539        }
1540    }
1541
1542    /**
1543     * Class for defining ActionListener for ACTION_DELAYED_TURNOUT
1544     */
1545    class TimeTurnout implements java.awt.event.ActionListener {
1546
1547        public TimeTurnout(int index) {
1548            mIndex = index;
1549        }
1550
1551        private int mIndex = 0;
1552
1553        @Override
1554        public void actionPerformed(java.awt.event.ActionEvent event) {
1555            // set turnout state
1556            ConditionalAction action = _actionList.get(mIndex);
1557            if (action.getNamedBean() == null) {
1558                log.error("{} Invalid delayed turnout name - {}", getDisplayName(), action.getDeviceName());  // NOI18N
1559            } else {
1560                Turnout t = (Turnout) action.getNamedBean().getBean();
1561                int act = action.getActionData();
1562                if (act == Route.TOGGLE) {
1563                    // toggle from current state
1564                    int state = t.getKnownState();
1565                    if (state == Turnout.CLOSED) {
1566                        act = Turnout.THROWN;
1567                    } else {
1568                        act = Turnout.CLOSED;
1569                    }
1570                }
1571                // set the turnout
1572                t.setCommandedState(act);
1573            }
1574            // Turn Timer OFF
1575            action.stopTimer();
1576        }
1577    }
1578
1579    private final static Logger log = LoggerFactory.getLogger(DefaultConditional.class);
1580}