001package jmri;
002
003import java.util.Date;
004import java.util.ResourceBundle;
005import java.text.MessageFormat;
006
007import javax.annotation.CheckForNull;
008import javax.annotation.Nonnull;
009
010import jmri.Conditional.Operator;
011import jmri.jmrit.beantable.LogixTableAction;
012import jmri.jmrit.logix.OBlock;
013import jmri.jmrit.logix.Warrant;
014import jmri.jmrit.logix.WarrantManager;
015
016/**
017 * The variable used in the antecedent (the 'if' part) of the Conditional.
018 * proposition. The states of ConditionalVariables and logic expression of the
019 * antecedent determine the state of the Conditional.
020 * <p>
021 * ConditionalVariable objects are fully mutable, so use the default equals()
022 * operator that checks for identical objects, not identical contents.
023 *
024 * This file is part of JMRI.
025 * <p>
026 * JMRI is free software; you can redistribute it and/or modify it under the
027 * terms of version 2 of the GNU General Public License as published by the Free
028 * Software Foundation. See the "COPYING" file for a copy of this license.
029 * <p>
030 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
031 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
032 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
033 *
034 * @author Pete Cressman Copyright (C) 2009
035 * @author Bob Jacobsen Copyright (C) 2016
036 */
037public class ConditionalVariable {
038
039    static final ResourceBundle rbx = ResourceBundle.getBundle("jmri.jmrit.conditional.ConditionalBundle");
040
041    public static final int NUM_COMPARE_OPERATIONS = 5;
042    public static final int LESS_THAN = 1;
043    public static final int LESS_THAN_OR_EQUAL = 2;
044    public static final int EQUAL = 3;
045    public static final int GREATER_THAN_OR_EQUAL = 4;
046    public static final int GREATER_THAN = 5;
047
048    private boolean _not = false;
049    // Not a variable attribute, but retained as an artifact of previous releases.  This will be used
050    // as the default operator immediately to the left of this variable in the antecedent statement.
051    // It may be over written by the antecedent statement in the Conditional to which this variable
052    // belongs.
053    private Operator _opern = Operator.NONE;
054    private Conditional.Type _type = Conditional.Type.NONE;
055    private String _name = "";
056    private String _dataString = "";
057    private int _num1 = 0;
058    private int _num2 = 0;
059    private String _guiName = "";       // Contains the user name of the referenced conditional
060    private NamedBeanHandle<?> _namedBean = null;
061    //private NamedBeanHandle<Sensor> _namedSensorBean = null;
062    protected NamedBeanHandleManager nbhm = InstanceManager.getDefault(NamedBeanHandleManager.class);
063    // Name clarification: Formerly was named '_triggersCalculation' because it controlled whether
064    // a listener was installed for this device and thus trigger calculation of the Conditional.
065    // Now named '_triggersActions' because listeners are always installed for activated Logix
066    // Conditionals and this parameter nows controls whether, if its change of state changes the
067    // state of the conditional, should that also  trigger the actions.
068    private boolean _triggersActions = true;
069    private int _state = NamedBean.UNKNOWN;        // tri-state
070
071    /**
072     * Create a blank ConditionalVariable, to be filled in later.
073     */
074    public ConditionalVariable() {
075    }
076
077    /**
078     * Create a ConditionalVariable with a set of given properties.
079     * @param not true if the ConditionalVariable should be negated
080     * @param opern the boolean operator for this ConditionalVariable
081     * @param type the type this ConditionalVariable operates on (Turnout, Sensor, ...)
082     * @param name the device name
083     * @param trigger true if actions should be performed if triggered
084     */
085    public ConditionalVariable(boolean not, @Nonnull Operator opern, @Nonnull Conditional.Type type,
086            String name, boolean trigger) {
087        _not = not;
088        // setOpern does some checks of opern
089        _opern = opern;
090        _type = type;
091        _name = name;
092        _triggersActions = trigger;
093        _guiName = "";
094        try {
095            Conditional.ItemType itemType = type.getItemType();
096            switch (itemType) {
097                case SENSOR:
098                    try {
099                        Sensor sn = InstanceManager.sensorManagerInstance().provideSensor(_name);
100                        _namedBean = nbhm.getNamedBeanHandle(_name, sn);
101                    } catch (IllegalArgumentException e) {
102                        log.error("invalid sensor name= \"{}\" in state variable", _name);
103                    }
104                    break;
105                case TURNOUT:
106                    try {
107                        Turnout tn = InstanceManager.turnoutManagerInstance().provideTurnout(_name);
108                        _namedBean = nbhm.getNamedBeanHandle(_name, tn);
109                    } catch (IllegalArgumentException e) {
110                        log.error("invalid turnout name= \"{}\" in state variable", _name);
111                    }
112                    break;
113                case MEMORY:
114                    try {
115                        Memory my = InstanceManager.memoryManagerInstance().provideMemory(_name);
116                        _namedBean = nbhm.getNamedBeanHandle(_name, my);
117                    } catch (IllegalArgumentException e) {
118                        log.error("invalid memory name= \"{}\" in state variable", _name);
119                    }
120                    break;
121                case LIGHT:
122                    try {
123                        Light l = InstanceManager.lightManagerInstance().provideLight(_name);
124                        _namedBean = nbhm.getNamedBeanHandle(_name, l);
125                    } catch (IllegalArgumentException e) {
126                        log.error("invalid light name= \"{}\" in state variable", _name);
127                    }
128                    break;
129                case SIGNALHEAD:
130                    SignalHead s = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(_name);
131                    if (s == null) {
132                        log.error("invalid signalhead name= \"{}\" in state variable", _name);
133                        return;
134                    }
135                    _namedBean = nbhm.getNamedBeanHandle(_name, s);
136                    break;
137                case SIGNALMAST:
138                    try {
139                        SignalMast sm = InstanceManager.getDefault(SignalMastManager.class).provideSignalMast(_name);
140                        _namedBean = nbhm.getNamedBeanHandle(_name, sm);
141                    } catch (IllegalArgumentException e) {
142                        log.error("invalid signalmast name= \"{}\" in state variable", _name);
143                    }
144                    break;
145                case ENTRYEXIT:
146                    NamedBean nb = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getBySystemName(_name);
147                    if (nb == null) {
148                        log.error("invalid entry exit name= \"{}\" in state variable", _name);
149                        return;
150                    }
151                    _namedBean = nbhm.getNamedBeanHandle(_name, nb);
152                    break;
153                case CONDITIONAL:
154                    Conditional c = InstanceManager.getDefault(ConditionalManager.class).getConditional(_name);
155                    if (c == null) {
156                        log.error("invalid conditional; name= \"{}\" in state variable", _name);
157                        return;
158                    }
159                    _namedBean = nbhm.getNamedBeanHandle(_name, c);
160                    break;
161                case WARRANT:
162                    Warrant w = InstanceManager.getDefault(WarrantManager.class).getWarrant(_name);
163                    if (w == null) {
164                        log.error("invalid warrant name= \"{}\" in state variable", _name);
165                        return;
166                    }
167                    _namedBean = nbhm.getNamedBeanHandle(_name, w);
168                    break;
169                case OBLOCK:
170                    OBlock b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(_name);
171                    if (b == null) {
172                        log.error("invalid block name= \"{}\" in state variable", _name);
173                        return;
174                    }
175                    _namedBean = nbhm.getNamedBeanHandle(_name, b);
176                    break;
177
178                default:
179                    log.warn("Unexpected type in ConditionalVariable ctor: {} -> {}", _type, itemType);
180                    break;
181            }
182        } catch (java.lang.NumberFormatException ex) {
183            //Can be Considered Normal where the logix is loaded prior to any other beans
184        } catch (IllegalArgumentException ex) {
185            log.warn("could not provide \"{}\" in constructor", _name);
186            _namedBean = null;
187        }
188    }
189
190    public boolean isNegated() {
191        return _not;
192    }
193
194    public void setNegation(boolean not) {
195        _not = not;
196    }
197
198    @Nonnull
199    public Operator getOpern() {
200        return _opern;
201    }
202
203    public final void setOpern(@Nonnull Operator opern) {
204        _opern = opern;
205    }
206
207    @Nonnull
208    public Conditional.Type getType() {
209        return _type;
210    }
211
212    public void setType(@Nonnull Conditional.Type type) {
213        _type = type;
214    }
215
216    public String getName() {
217        if (_namedBean != null) {
218            return _namedBean.getName();
219        }
220        /* As we have a trigger for something using the variable, then hopefully
221         all the managers have been loaded and we can get the bean, which prevented
222         the bean from being loaded in the first place */
223        setName(_name);
224        return _name;
225    }
226
227    public void setName(String name) {
228        _name = name;
229        NamedBean bean = null;
230        Conditional.ItemType itemType = _type.getItemType();
231
232        try {
233            switch (itemType) {
234                case NONE:
235                    break;
236                case CLOCK:
237                    break; // no beans for these, at least that I know of
238                case SENSOR:
239                    bean = InstanceManager.sensorManagerInstance().provideSensor(_name);
240                    break;
241                case TURNOUT:
242                    bean = InstanceManager.turnoutManagerInstance().provideTurnout(_name);
243                    break;
244                case LIGHT:
245                    bean = InstanceManager.lightManagerInstance().getLight(_name);
246                    break;
247                case MEMORY:
248                    bean = InstanceManager.memoryManagerInstance().provideMemory(_name);
249                    break;
250                case SIGNALMAST:
251                    bean = InstanceManager.getDefault(SignalMastManager.class).provideSignalMast(_name);
252                    break;
253                case SIGNALHEAD:
254                    bean = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(_name);
255                    break;
256                case CONDITIONAL:
257                    bean = InstanceManager.getDefault(ConditionalManager.class).getConditional(_name);
258                    break;
259                case WARRANT:
260                    bean = InstanceManager.getDefault(WarrantManager.class).getWarrant(_name);
261                    break;
262                case OBLOCK:
263                    bean = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(_name);
264                    break;
265                case ENTRYEXIT:
266                    bean = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(_name);
267                    break;
268                default:
269                    log.error("Type {} not set for {}", itemType, _name);
270            }
271
272            //Once all refactored, we should probably register an error if the bean is returned null.
273            if (bean != null) {
274                _namedBean = nbhm.getNamedBeanHandle(_name, bean);
275            } else {
276                log.debug("Did not have or create \"{}\" in setName. namedBean is unchanged", _name);
277            }
278
279        } catch (IllegalArgumentException ex) {
280            log.warn("Did not have or create \"{}\" in setName", _name);
281            _namedBean = null;
282        }
283    }
284
285    @CheckForNull
286    public NamedBeanHandle<?> getNamedBean() {
287        return _namedBean;
288    }
289
290    @CheckForNull
291    public NamedBean getBean() {
292        if (_namedBean != null) {
293            return _namedBean.getBean();
294        }
295        setName(_name); //ReApply name as that will create namedBean, save replicating it here
296        if (_namedBean != null) {
297            return _namedBean.getBean();
298        }
299        return null;
300    }
301
302    public String getDataString() {
303        if (_type.getItemType() == Conditional.ItemType.MEMORY
304                && _namedBeanData != null) {
305            return _namedBeanData.getName();
306        }
307        return _dataString;
308    }
309
310    public void setDataString(@CheckForNull String data) {
311        _dataString = data;
312        if (data != null && !data.isEmpty()
313                && _type.getItemType() == Conditional.ItemType.MEMORY) {
314            NamedBean bean = InstanceManager.memoryManagerInstance().getMemory(data);
315            if (bean != null) {
316                _namedBeanData = nbhm.getNamedBeanHandle(data, bean);
317            }
318        }
319    }
320
321    private NamedBeanHandle<?> _namedBeanData = null;
322
323    @CheckForNull
324    public NamedBean getNamedBeanData() {
325        if (_namedBeanData != null) {
326            return _namedBeanData.getBean();
327        }
328        return null;
329    }
330
331    /**
332     * Get Data 1.
333     * Used for Fast Clock Start time,
334     * and value for Memory expression type, e.g. LESS_THAN or GREATER_THAN
335     * @return value for Data 1.
336     */
337    public int getNum1() {
338        return _num1;
339    }
340
341    /**
342     * Set Data 1.
343     * Used for Fast Clock Start time and Memoey expression operator value.
344     * @param num the value.
345     */
346    public void setNum1(int num) {
347        _num1 = num;
348    }
349
350    /**
351     * Get Data 2.
352     * Used for Fast Clock Finish time.
353     * @return value for Data 2.
354     */
355    public int getNum2() {
356        return _num2;
357    }
358
359    /**
360     * Set Data 2.
361     * Used for Fast Clock Finish time.
362     * @param num the value.
363     */
364    public void setNum2(int num) {
365        _num2 = num;
366    }
367
368     /**
369     * @since 4.7.4
370     * @return the GUI name for the referenced conditional.
371     */
372    public String getGuiName() {
373        return _guiName;
374    }
375
376    /**
377     * Set the GUI name for the conditional state variable.
378     * @since 4.7.4
379     * @param guiName The referenced Conditional user name.
380     */
381    public void setGuiName(String guiName) {
382        _guiName = guiName;
383    }
384
385
386    /**
387     * If change of state of this object causes a change of state of the
388     * Conditional, should any actions be executed.
389     *
390     * @return true if actions should be performed if triggered
391     */
392    public boolean doTriggerActions() {
393        return _triggersActions;
394    }
395
396    public void setTriggerActions(boolean trigger) {
397        _triggersActions = trigger;
398    }
399
400    public int getState() {
401        return _state;
402    }
403
404    public void setState(int state) {
405        _state = state;
406    }
407
408    public void setState(boolean state) {
409        _state = state ? Conditional.TRUE : Conditional.FALSE;
410    }
411
412    public String getTestTypeString() {
413        return _type.getTestTypeString();
414    }
415
416    /**
417     * Provide a localized text for screen display of the logic operator.
418     *
419     * @return translated string (from jmri.NamedBeanBundle.properties)
420     */
421    @Nonnull
422    public String getOpernString() {
423        switch (_opern) {
424            case AND:
425                return Bundle.getMessage("LogicAND"); // NOI18N
426            case NONE:
427                return "";
428            case OR:
429                return Bundle.getMessage("LogicOR"); // NOI18N
430            default:
431                return "";
432        }
433    }
434
435    /**
436     * Evaluates this State Variable.
437     *
438     * @return true if variable evaluates true, otherwise false.
439     */
440    @SuppressWarnings("deprecation")        // Date.getMinutes, Date.getHours
441    public boolean evaluate() {
442        boolean result = true;
443        // evaluate according to state variable type
444        Conditional.ItemType itemType = _type.getItemType();
445        log.debug("evaluate: \"{}\" type= {} itemType= {}", getName(), _type, itemType);
446        switch (itemType) {
447            case SENSOR:
448                Sensor sn = (Sensor) getBean();
449                if (sn == null) {
450                    log.error("invalid sensor name= \"{}\" in state variable", getName());
451                    return false;
452                }
453                if (_type == Conditional.Type.SENSOR_ACTIVE) {
454                    result = sn.getState() == Sensor.ACTIVE;
455                } else {
456                    result = sn.getState() == Sensor.INACTIVE;
457                }
458                break;
459            case TURNOUT:
460                Turnout t = (Turnout) getBean();
461                if (t == null) {
462                    log.error("invalid turnout name= \"{}\" in state variable", getName());
463                    return false;
464                }
465                if (_type == Conditional.Type.TURNOUT_THROWN) {
466                    result = t.getKnownState() == Turnout.THROWN;
467                } else {
468                    result = t.getKnownState() == Turnout.CLOSED;
469                }
470                break;
471            case LIGHT:
472                Light lgt = (Light) getBean();
473                if (lgt == null) {
474                    log.error("invalid light name= \"{}\" in state variable", getName());
475                    return false;
476                }
477                if (_type == Conditional.Type.LIGHT_ON) {
478                    result = lgt.getState() == Light.ON;
479                } else {
480                    result = lgt.getState() == Light.OFF;
481                }
482                break;
483            case SIGNALMAST:
484                SignalMast f = (SignalMast) getBean();
485                if (f == null) {
486                    log.error("invalid signal mast name= \"{}\" in state variable", getName());
487                    return false;
488                }
489                switch (_type) {
490                    case SIGNAL_MAST_LIT:
491                        result = f.getLit();
492                        break;
493                    case SIGNAL_MAST_HELD:
494                        result = f.getHeld();
495                        break;
496                    case SIGNAL_MAST_ASPECT_EQUALS:
497                        String aspect = f.getAspect();
498                        if ( aspect == null) {
499                            result = false;
500                        } else {
501                            result = aspect.equals(_dataString);
502                        }
503                        break;
504                    default:
505                        log.warn("unexpected type {} in ITEM_TYPE_SIGNALMAST", _type);
506                }
507                break;
508            case SIGNALHEAD:
509                SignalHead h = (SignalHead) getBean();
510                if (h == null) {
511                    log.error("invalid signal head name= \"{}\" in state variable", getName());
512                    return false;
513                }
514                switch (_type) {
515                    case SIGNAL_HEAD_RED:
516                        result = h.getAppearance() == SignalHead.RED;
517                        break;
518                    case SIGNAL_HEAD_YELLOW:
519                        result = h.getAppearance() == SignalHead.YELLOW;
520                        break;
521                    case SIGNAL_HEAD_GREEN:
522                        result = h.getAppearance() == SignalHead.GREEN;
523                        break;
524                    case SIGNAL_HEAD_DARK:
525                        result = h.getAppearance() == SignalHead.DARK;
526                        break;
527                    case SIGNAL_HEAD_FLASHRED:
528                        result = h.getAppearance() == SignalHead.FLASHRED;
529                        break;
530                    case SIGNAL_HEAD_FLASHYELLOW:
531                        result = h.getAppearance() == SignalHead.FLASHYELLOW;
532                        break;
533                    case SIGNAL_HEAD_FLASHGREEN:
534                        result = h.getAppearance() == SignalHead.FLASHGREEN;
535                        break;
536                    case SIGNAL_HEAD_LUNAR:
537                        result = h.getAppearance() == SignalHead.LUNAR;
538                        break;
539                    case SIGNAL_HEAD_FLASHLUNAR:
540                        result = h.getAppearance() == SignalHead.FLASHLUNAR;
541                        break;
542                    case SIGNAL_HEAD_LIT:
543                        result = h.getLit();
544                        break;
545                    case SIGNAL_HEAD_HELD:
546                        result = h.getHeld();
547                        break;
548                    default:
549                        result = false;
550                }
551                break;
552            case MEMORY:
553                Memory m = (Memory) getBean();
554                if (m == null) {
555                    log.error("invalid memory name= \"{}\" in state variable", getName());
556                    return false;
557                }
558                String value1 = null;
559                String value2 = null;
560                if (m.getValue() != null) {
561                    value1 = m.getValue().toString();
562                }
563                boolean caseInsensitive = ((_type == Conditional.Type.MEMORY_EQUALS_INSENSITIVE)
564                        || (_type == Conditional.Type.MEMORY_COMPARE_INSENSITIVE));
565                if ((_type == Conditional.Type.MEMORY_COMPARE)
566                        || (_type == Conditional.Type.MEMORY_COMPARE_INSENSITIVE)) {
567                    Memory m2;
568                    if (_namedBeanData != null) {
569                        m2 = (Memory) _namedBeanData.getBean();
570                    } else {
571                        try {
572                            m2 = InstanceManager.memoryManagerInstance().provideMemory(_dataString);
573                        } catch (IllegalArgumentException ex) {
574                            log.error("invalid data memory name= \"{}\" in state variable", _dataString);
575                            return false;
576                        }
577                    }
578                    if (m2.getValue() != null) {
579                        value2 = m2.getValue().toString();
580                    }
581                } else {
582                    value2 = _dataString;
583                }
584                result = compare(value1, value2, caseInsensitive);
585                break;
586            case CONDITIONAL:
587                Conditional c = InstanceManager.getDefault(ConditionalManager.class).getBySystemName(getName());
588                if (c == null) {
589                    c = InstanceManager.getDefault(ConditionalManager.class).getByUserName(getName());
590                    if (c == null) {
591                        log.error("invalid conditional name= \"{}\" in state variable", getName());
592                        return false;
593                    }
594                }
595                if (_type == Conditional.Type.CONDITIONAL_TRUE) {
596                    result = c.getState() == Conditional.TRUE;
597                } else {
598                    result = c.getState() == Conditional.FALSE;
599                }
600                break;
601            case WARRANT:
602                Warrant w = InstanceManager.getDefault(WarrantManager.class).getWarrant(getName());
603                if (w == null) {
604                    log.error("invalid Warrant name= \"{}\" in state variable", getName());
605                    return false;
606                }
607                switch (_type) {
608                    case ROUTE_FREE:
609                        result = w.routeIsFree();
610                        break;
611                    case ROUTE_OCCUPIED:
612                        result = w.routeIsOccupied();
613                        break;
614                    case ROUTE_ALLOCATED:
615                        result = w.isAllocated();
616                        break;
617                    case ROUTE_SET:
618                        result = w.hasRouteSet();
619                        break;
620                    case TRAIN_RUNNING:
621                        // not in either RUN or LEARN state
622                        result = (w.getRunMode() != Warrant.MODE_NONE);
623                        break;
624                    default:
625                        result = false;
626                }
627                break;
628            case CLOCK:
629                Timebase fastClock = InstanceManager.getDefault(Timebase.class);
630                Date currentTime = fastClock.getTime();
631                int currentMinutes = (currentTime.getHours() * 60) + currentTime.getMinutes();
632                int beginTime = fixMidnight(_num1);
633                int endTime = fixMidnight(_num2);
634                // check if current time is within range specified
635                if (beginTime <= endTime) {
636                    // range is entirely within one day
637                    result = (beginTime <= currentMinutes) && (currentMinutes <= endTime);
638                } else {
639                    // range includes midnight
640                    result = beginTime <= currentMinutes || currentMinutes <= endTime;
641                }
642                break;
643            case OBLOCK:
644                OBlock b = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(getName());
645                if (b == null) {
646                    log.error("invalid OBlock name= \"{}\" in state variable", getName());
647                    return false;
648                }
649                result = b.statusIs(_dataString);
650                break;
651            case ENTRYEXIT:
652                NamedBean e = getBean();
653                if ( e == null ) {
654                    log.error("invalid EntryExit name= \"{}\" in state variable", getName());
655                    return false;
656                }
657                if (_type == Conditional.Type.ENTRYEXIT_ACTIVE) {
658                    result = e.getState() == 0x02;
659                } else {
660                    result = e.getState() == 0x04;
661                }
662                break;
663            default:
664                break;
665        }
666        // apply NOT if specified
667        if (_not) {
668            result = !result;
669        }
670        setState( result );
671        return result;
672    }
673
674    /**
675     * Compare two values using the comparator set using the comparison
676     * instructions in {@link #setNum1(int)}.
677     *
678     * <strong>Note:</strong> {@link #getNum1()} must be one of {@link #LESS_THAN},
679     * {@link #LESS_THAN_OR_EQUAL}, {@link #EQUAL},
680     * {@link #GREATER_THAN_OR_EQUAL}, or {@link #GREATER_THAN}.
681     *
682     * @param value1          left side of the comparison
683     * @param value2          right side of the comparison
684     * @param caseInsensitive true if comparison should be case insensitive;
685     *                        false otherwise
686     * @return true if values compare per getNum1(); false otherwise
687     */
688    boolean compare(@CheckForNull String value1, @CheckForNull String value2, boolean caseInsensitive) {
689        if (value1 == null) {
690            return value2 == null;
691        } else {
692            if (value2 == null) {
693                return false;
694            }
695            value1 = value1.trim();
696            value2 = value2.trim();
697        }
698        try {
699            int n1 = Integer.parseInt(value1);
700            try {
701                int n2 = Integer.parseInt(value2);
702                if (_num1 == 0) { // for former code
703                    return n1 == n2;
704                }
705                log.debug("Compare numbers: n1= {} to n2= {}", n1, n2);
706                switch (_num1) // both are numbers
707                {
708                    case LESS_THAN:
709                        return (n1 < n2);
710                    case LESS_THAN_OR_EQUAL:
711                        return (n1 <= n2);
712                    case EQUAL:
713                        return (n1 == n2);
714                    case GREATER_THAN_OR_EQUAL:
715                        return (n1 >= n2);
716                    case GREATER_THAN:
717                        return (n1 > n2);
718                    default:
719                        log.error("Compare numbers: invalid compare case: {}", _num1);
720                        return false;
721                }
722            } catch (NumberFormatException nfe) {
723                return false;   // n1 is a number, n2 is not
724            }
725        } catch (NumberFormatException nfe) {
726            try {
727                Integer.parseInt(value2);
728                return false;     // n1 is not a number, n2 is
729            } catch (NumberFormatException ex) { // OK neither a number
730            }
731        }
732        log.debug("Compare Strings: value1= {} to value2= {}", value1, value2);
733        int compare;
734        if (caseInsensitive) {
735            compare = value1.compareToIgnoreCase(value2);
736        } else {
737            compare = value1.compareTo(value2);
738        }
739        if (_num1 == 0) { // for former code
740            return compare == 0;
741        }
742        switch (_num1) {
743            case LESS_THAN:
744                if (compare < 0) {
745                    return true;
746                }
747                break;
748            case LESS_THAN_OR_EQUAL:
749                if (compare <= 0) {
750                    return true;
751                }
752                break;
753            case EQUAL:
754                if (compare == 0) {
755                    return true;
756                }
757                break;
758            case GREATER_THAN_OR_EQUAL:
759                if (compare >= 0) {
760                    return true;
761                }
762                break;
763            case GREATER_THAN:
764                if (compare > 0) {
765                    return true;
766                }
767                break;
768            default:
769                // fall through
770                break;
771        }
772        return false;
773    }
774
775    public static int fixMidnight(int time) {
776        if (time > 24 * 60) {
777            return time - ( 24 * 60 );
778        }
779        return time;
780    }
781
782    /**
783     * Convert Variable Type to Text String
784     *
785     * @param t the type
786     * @return the localized description
787     */
788    @Nonnull
789    public static String getItemTypeString( @Nonnull Conditional.ItemType t) {
790        switch (t) {
791            case SENSOR:
792                return Bundle.getMessage("BeanNameSensor"); // NOI18N
793            case TURNOUT:
794                return Bundle.getMessage("BeanNameTurnout"); // NOI18N
795            case LIGHT:
796                return Bundle.getMessage("BeanNameLight"); // NOI18N
797            case SIGNALHEAD:
798                return Bundle.getMessage("BeanNameSignalHead"); // NOI18N
799            case SIGNALMAST:
800                return Bundle.getMessage("BeanNameSignalMast"); // NOI18N
801            case MEMORY:
802                return Bundle.getMessage("BeanNameMemory"); // NOI18N
803            case CONDITIONAL:
804                return Bundle.getMessage("BeanNameConditional"); // NOI18N
805            case WARRANT:
806                return Bundle.getMessage("BeanNameWarrant"); // NOI18N
807            case CLOCK:
808                return Bundle.getMessage("FastClock"); // NOI18N
809            case OBLOCK:
810                return Bundle.getMessage("BeanNameOBlock"); // NOI18N
811            case ENTRYEXIT:
812                return Bundle.getMessage("BeanNameEntryExit"); // NOI18N
813            default:
814                return "";
815        }
816    }
817
818    @Nonnull
819    public static String getCompareOperationString(int index) {
820        switch (index) {
821            case LESS_THAN:
822                return rbx.getString("LessThan"); // NOI18N
823            case LESS_THAN_OR_EQUAL:
824                return rbx.getString("LessOrEqual"); // NOI18N
825            case 0:
826            case EQUAL:
827                return rbx.getString("Equal"); // NOI18N
828            case GREATER_THAN_OR_EQUAL:
829                return rbx.getString("GreaterOrEqual"); // NOI18N
830            case GREATER_THAN:
831                return rbx.getString("GreaterThan"); // NOI18N
832            default:
833                // fall through
834                break;
835        }
836        return ""; // NOI18N
837    }
838
839    @Nonnull
840    public static String getCompareSymbols(int index) {
841        switch (index) {
842            case LESS_THAN:
843                return "<"; // NOI18N
844            case LESS_THAN_OR_EQUAL:
845                return "<="; // NOI18N
846            case 0:
847            case EQUAL:
848                return "="; // NOI18N
849            case GREATER_THAN_OR_EQUAL:
850                return ">="; // NOI18N
851            case GREATER_THAN:
852                return ">"; // NOI18N
853            default:
854                break;
855        }
856        return ""; // NOI18N
857    }
858
859    /**
860     * Identify action Data from Text String.
861     *
862     * @param str the text to check
863     * @return the conditional action type or -1 if if string does not
864     * correspond to an action Data as defined in ConditionalAction
865     */
866    @Nonnull
867    public static Conditional.Type stringToVariableTest(@Nonnull String str) {
868        if (str.equals(Bundle.getMessage("SignalHeadStateRed"))) { // NOI18N
869            return Conditional.Type.SIGNAL_HEAD_RED;
870        } else if (str.equals(Bundle.getMessage("SignalHeadStateYellow"))) { // NOI18N
871            return Conditional.Type.SIGNAL_HEAD_YELLOW;
872        } else if (str.equals(Bundle.getMessage("SignalHeadStateGreen"))) { // NOI18N
873            return Conditional.Type.SIGNAL_HEAD_GREEN;
874        } else if (str.equals(Bundle.getMessage("SignalHeadStateDark"))) { // NOI18N
875            return Conditional.Type.SIGNAL_HEAD_DARK;
876        } else if (str.equals(Bundle.getMessage("SignalHeadStateFlashingRed"))) { // NOI18N
877            return Conditional.Type.SIGNAL_HEAD_FLASHRED;
878        } else if (str.equals(Bundle.getMessage("SignalHeadStateFlashingYellow"))) { // NOI18N
879            return Conditional.Type.SIGNAL_HEAD_FLASHYELLOW;
880        } else if (str.equals(Bundle.getMessage("SignalHeadStateFlashingGreen"))) { // NOI18N
881            return Conditional.Type.SIGNAL_HEAD_FLASHGREEN;
882        } else if (str.equals(Bundle.getMessage("SignalHeadStateLunar"))) { // NOI18N
883            return Conditional.Type.SIGNAL_HEAD_LUNAR;
884        } else if (str.equals(Bundle.getMessage("SignalHeadStateFlashingLunar"))) { // NOI18N
885            return Conditional.Type.SIGNAL_HEAD_FLASHLUNAR;
886        }
887        // empty strings can occur frequently with types that have no integer data
888        if (str.length() > 0) {
889            log.warn("Unexpected parameter to stringToVariableTest({})", str);
890        }
891        return Conditional.Type.ERROR;
892    }
893
894    @Override
895    public String toString() {
896        String type = _type.getTestTypeString();
897        Conditional.ItemType itemType = _type.getItemType();
898        switch (itemType) {
899            case SENSOR:
900                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
901                    Bundle.getMessage("BeanNameSensor"), getName(), type );
902            case TURNOUT:
903                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
904                    Bundle.getMessage("BeanNameTurnout"), getName(), type);
905            case LIGHT:
906                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
907                    Bundle.getMessage("BeanNameLight"), getName(), type );
908            case SIGNALHEAD:
909                if ((_type == Conditional.Type.SIGNAL_HEAD_LIT)
910                        || (_type == Conditional.Type.SIGNAL_HEAD_HELD)) {
911                    return MessageFormat.format(rbx.getString("VarStateDescrpt"),
912                        Bundle.getMessage("BeanNameSignalHead"), getName(), type );
913                } else {
914                    return MessageFormat.format(rbx.getString("SignalHeadStateDescrpt"),
915                        Bundle.getMessage("BeanNameSignalHead"), getName(), type );
916                }
917            case SIGNALMAST:
918                if ((_type == Conditional.Type.SIGNAL_MAST_LIT)
919                        || (_type == Conditional.Type.SIGNAL_MAST_HELD)) {
920                    return MessageFormat.format(rbx.getString("VarStateDescrpt"),
921                        Bundle.getMessage("BeanNameSignalMast"), getName(), type );
922                } else {
923                    return MessageFormat.format(rbx.getString("SignalMastStateDescrpt"),
924                        Bundle.getMessage("BeanNameSignalMast"), getName(), _dataString );
925                }
926            case MEMORY:
927                if ((_type == Conditional.Type.MEMORY_EQUALS)
928                        || (_type == Conditional.Type.MEMORY_EQUALS_INSENSITIVE)) {
929                    return MessageFormat.format(rbx.getString("MemoryValueDescrpt"),
930                        Bundle.getMessage("BeanNameMemory"), getName(),
931                        getCompareSymbols(_num1), _dataString);
932                } else {
933                    return MessageFormat.format(rbx.getString("MemoryCompareDescrpt"),
934                        Bundle.getMessage("BeanNameMemory"), getName(),
935                        getCompareSymbols(_num1), _dataString);
936                }
937            case CONDITIONAL:
938                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
939                    Bundle.getMessage("BeanNameConditional"), getGuiName(), type );
940            case WARRANT:
941                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
942                    rbx.getString("WarrantRoute"), getName(), type );
943            case CLOCK:
944                return MessageFormat.format(rbx.getString("FastClockDescrpt"),
945                    Bundle.getMessage("FastClock"),
946                    LogixTableAction.formatTime(_num1 / 60, _num1 - ((_num1 / 60) * 60)),
947                    LogixTableAction.formatTime(_num2 / 60, _num2 - ((_num2 / 60) * 60)));
948            case OBLOCK:
949                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
950                    rbx.getString("OBlockStatus"), getName(), _dataString );
951            case ENTRYEXIT:
952                NamedBean nb = getBean();
953                return MessageFormat.format(rbx.getString("VarStateDescrpt"),
954                    Bundle.getMessage("BeanNameEntryExit"),
955                    nb == null ? "<NULL>" : nb.getUserName(), type);
956            case NONE:
957                return getName() + " type " + type;
958            default:
959                // fall through
960                break;
961        }
962        return super.toString();
963    }
964
965    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalVariable.class);
966}