001package jmri.jmrit.logixng.expressions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.beans.PropertyVetoException;
006import java.beans.VetoableChangeListener;
007import java.util.*;
008import java.util.regex.Matcher;
009import java.util.regex.Pattern;
010
011import javax.annotation.CheckForNull;
012import javax.annotation.Nonnull;
013
014import jmri.*;
015import jmri.jmrit.logixng.*;
016import jmri.util.TypeConversionUtil;
017
018/**
019 * Evaluates the state of a local variable.
020 *
021 * @author Daniel Bergqvist Copyright 2020
022 */
023public class ExpressionLocalVariable extends AbstractDigitalExpression
024        implements PropertyChangeListener, VetoableChangeListener {
025
026    private String _localVariable;
027    private VariableOperation _variableOperation = VariableOperation.Equal;
028    private CompareTo _compareTo = CompareTo.Value;
029    private boolean _caseInsensitive = false;
030    private String _constantValue = "";
031    private NamedBeanHandle<Memory> _memoryHandle;
032    private String _otherLocalVariable = "";
033    private String _regEx = "";
034    private boolean _listenToMemory = true;
035//    private boolean _listenToMemory = false;
036
037    public ExpressionLocalVariable(String sys, String user)
038            throws BadUserNameException, BadSystemNameException {
039        super(sys, user);
040    }
041
042    @Override
043    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
044        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
045        String sysName = systemNames.get(getSystemName());
046        String userName = userNames.get(getSystemName());
047        if (sysName == null) sysName = manager.getAutoSystemName();
048        ExpressionLocalVariable copy = new ExpressionLocalVariable(sysName, userName);
049        copy.setComment(getComment());
050        copy.setLocalVariable(_localVariable);
051        copy.setVariableOperation(_variableOperation);
052        copy.setCompareTo(_compareTo);
053        copy.setCaseInsensitive(_caseInsensitive);
054        copy.setConstantValue(_constantValue);
055        if (_memoryHandle != null) copy.setMemory(_memoryHandle);
056        copy.setOtherLocalVariable(_localVariable);
057        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
058    }
059
060    public void setLocalVariable(String variableName) {
061        assertListenersAreNotRegistered(log, "setLocalVariable");
062        _localVariable = variableName;
063    }
064
065    public String getLocalVariable() {
066        return _localVariable;
067    }
068
069    public void setMemory(@Nonnull String memoryName) {
070        assertListenersAreNotRegistered(log, "setMemory");
071        MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class);
072        Memory memory = memoryManager.getMemory(memoryName);
073        if (memory != null) {
074            setMemory(memory);
075        } else {
076            removeMemory();
077            log.error("memory \"{}\" is not found", memoryName);
078        }
079    }
080
081    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
082        assertListenersAreNotRegistered(log, "setMemory");
083        _memoryHandle = handle;
084        if (_memoryHandle != null) {
085            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
086        } else {
087            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
088        }
089    }
090
091    public void setMemory(@CheckForNull Memory memory) {
092        assertListenersAreNotRegistered(log, "setMemory");
093        if (memory != null) {
094            _memoryHandle = InstanceManager.getDefault(NamedBeanHandleManager.class)
095                    .getNamedBeanHandle(memory.getDisplayName(), memory);
096            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
097        } else {
098            _memoryHandle = null;
099            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
100        }
101    }
102
103    public void removeMemory() {
104        assertListenersAreNotRegistered(log, "removeMemory");
105        if (_memoryHandle != null) {
106            InstanceManager.memoryManagerInstance().removeVetoableChangeListener(this);
107            _memoryHandle = null;
108        }
109    }
110
111    public NamedBeanHandle<Memory> getMemory() {
112        return _memoryHandle;
113    }
114
115    public void setOtherLocalVariable(@Nonnull String localVariable) {
116        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
117        _otherLocalVariable = localVariable;
118    }
119
120    public String getOtherLocalVariable() {
121        return _otherLocalVariable;
122    }
123
124    public void setConstantValue(String constantValue) {
125        _constantValue = constantValue;
126    }
127
128    public String getConstantValue() {
129        return _constantValue;
130    }
131
132    public void setRegEx(String regEx) {
133        _regEx = regEx;
134    }
135
136    public String getRegEx() {
137        return _regEx;
138    }
139
140    public void setListenToMemory(boolean listenToMemory) {
141        this._listenToMemory = listenToMemory;
142    }
143
144    public boolean getListenToMemory() {
145        return _listenToMemory;
146    }
147
148    public void setVariableOperation(VariableOperation variableOperation) {
149        _variableOperation = variableOperation;
150    }
151
152    public VariableOperation getVariableOperation() {
153        return _variableOperation;
154    }
155
156    public void setCompareTo(CompareTo compareTo) {
157        _compareTo = compareTo;
158    }
159
160    public CompareTo getCompareTo() {
161        return _compareTo;
162    }
163
164    public void setCaseInsensitive(boolean caseInsensitive) {
165        _caseInsensitive = caseInsensitive;
166    }
167
168    public boolean getCaseInsensitive() {
169        return _caseInsensitive;
170    }
171
172    @Override
173    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
174/*
175        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
176            if (evt.getOldValue() instanceof Memory) {
177                if (evt.getOldValue().equals(getMemory().getBean())) {
178                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
179                    throw new PropertyVetoException(Bundle.getMessage("Memory_MemoryInUseMemoryExpressionVeto", getDisplayName()), e); // NOI18N
180                }
181            }
182        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
183            if (evt.getOldValue() instanceof Memory) {
184                if (evt.getOldValue().equals(getMemory().getBean())) {
185                    removeMemory();
186                }
187            }
188        }
189*/
190    }
191
192    /** {@inheritDoc} */
193    @Override
194    public Category getCategory() {
195        return Category.ITEM;
196    }
197
198    private String getString(Object o) {
199        if (o != null) {
200            return o.toString();
201        }
202        return null;
203    }
204
205    /**
206     * Compare two values using the comparator set using the comparison
207     * instructions in {@link #_variableOperation}.
208     *
209     * <strong>Note:</strong> {@link #_variableOperation} must be one of
210     * {@link #ExpressionLocalVariable.MemoryOperation.LESS_THAN},
211     * {@link #ExpressionLocalVariable.MemoryOperation.LESS_THAN_OR_EQUAL},
212     * {@link #ExpressionLocalVariable.MemoryOperation.EQUAL},
213     * {@link #ExpressionLocalVariable.MemoryOperation.GREATER_THAN_OR_EQUAL},
214     * or {@link #ExpressionLocalVariable.MemoryOperation.GREATER_THAN}.
215     *
216     * @param value1          left side of the comparison
217     * @param value2          right side of the comparison
218     * @param caseInsensitive true if comparison should be case insensitive;
219     *                        false otherwise
220     * @return true if values compare per _memoryOperation; false otherwise
221     */
222    private boolean compare(String value1, String value2, boolean caseInsensitive) {
223        if (value1 == null) {
224            return value2 == null;
225        } else {
226            if (value2 == null) {
227                return false;
228            }
229            value1 = value1.trim();
230            value2 = value2.trim();
231        }
232        try {
233            int n1 = Integer.parseInt(value1);
234            try {
235                int n2 = Integer.parseInt(value2);
236                log.debug("Compare numbers: n1= {} to n2= {}", n1, n2);
237                switch (_variableOperation) // both are numbers
238                {
239                    case LessThan:
240                        return (n1 < n2);
241                    case LessThanOrEqual:
242                        return (n1 <= n2);
243                    case Equal:
244                        return (n1 == n2);
245                    case NotEqual:
246                        return (n1 != n2);
247                    case GreaterThanOrEqual:
248                        return (n1 >= n2);
249                    case GreaterThan:
250                        return (n1 > n2);
251                    default:
252                        throw new IllegalArgumentException("_memoryOperation has unknown value: "+_variableOperation.name());
253                }
254            } catch (NumberFormatException nfe) {
255                return _variableOperation == VariableOperation.NotEqual;   // n1 is a number, n2 is not
256            }
257        } catch (NumberFormatException nfe) {
258            try {
259                Integer.parseInt(value2);
260                return _variableOperation == VariableOperation.NotEqual;     // n1 is not a number, n2 is
261            } catch (NumberFormatException ex) { // OK neither a number
262            }
263        }
264        log.debug("Compare Strings: value1= {} to value2= {}", value1, value2);
265        int compare;
266        if (caseInsensitive) {
267            compare = value1.compareToIgnoreCase(value2);
268        } else {
269            compare = value1.compareTo(value2);
270        }
271        switch (_variableOperation) {
272            case LessThan:
273                if (compare < 0) {
274                    return true;
275                }
276                break;
277            case LessThanOrEqual:
278                if (compare <= 0) {
279                    return true;
280                }
281                break;
282            case Equal:
283                if (compare == 0) {
284                    return true;
285                }
286                break;
287            case NotEqual:
288                if (compare != 0) {
289                    return true;
290                }
291                break;
292            case GreaterThanOrEqual:
293                if (compare >= 0) {
294                    return true;
295                }
296                break;
297            case GreaterThan:
298                if (compare > 0) {
299                    return true;
300                }
301                break;
302            default:
303                throw new IllegalArgumentException("_memoryOperation has unknown value: "+_variableOperation.name());
304        }
305        return false;
306    }
307
308    private boolean matchRegex(String memoryValue, String regex) {
309        Pattern pattern = Pattern.compile(regex);
310        Matcher m = pattern.matcher(memoryValue);
311        return m.matches();
312    }
313
314    /** {@inheritDoc} */
315    @Override
316    public boolean evaluate() {
317        if (_localVariable == null) return false;
318
319        String variableValue = getString(getConditionalNG()
320                        .getSymbolTable().getValue(_localVariable));
321        String otherValue = null;
322        boolean result;
323
324        switch (_compareTo) {
325            case Value:
326                otherValue = _constantValue;
327                break;
328            case Memory:
329                otherValue = getString(_memoryHandle.getBean().getValue());
330                break;
331            case LocalVariable:
332                otherValue = TypeConversionUtil.convertToString(getConditionalNG().getSymbolTable().getValue(_otherLocalVariable), false);
333                break;
334            case RegEx:
335                // Do nothing
336                break;
337            default:
338                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
339        }
340
341        switch (_variableOperation) {
342            case LessThan:
343                // fall through
344            case LessThanOrEqual:
345                // fall through
346            case Equal:
347                // fall through
348            case NotEqual:
349                // fall through
350            case GreaterThanOrEqual:
351                // fall through
352            case GreaterThan:
353                result = compare(variableValue, otherValue, _caseInsensitive);
354                break;
355
356            case IsNull:
357                result = variableValue == null;
358                break;
359            case IsNotNull:
360                result = variableValue != null;
361                break;
362
363            case MatchRegex:
364                result = matchRegex(variableValue, _regEx);
365                break;
366
367            case NotMatchRegex:
368                result = !matchRegex(variableValue, _regEx);
369                break;
370
371            default:
372                throw new IllegalArgumentException("_memoryOperation has unknown value: "+_variableOperation.name());
373        }
374
375        return result;
376    }
377
378    @Override
379    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
380        throw new UnsupportedOperationException("Not supported.");
381    }
382
383    @Override
384    public int getChildCount() {
385        return 0;
386    }
387
388    @Override
389    public String getShortDescription(Locale locale) {
390        return Bundle.getMessage(locale, "LocalVariable_Short");
391    }
392
393    @Override
394    public String getLongDescription(Locale locale) {
395        String variableName;
396        if ((_localVariable == null) || _localVariable.isEmpty()) {
397            variableName = Bundle.getMessage(locale, "BeanNotSelected");
398        } else {
399            variableName = _localVariable;
400        }
401
402        String memoryName;
403        if (_memoryHandle != null) {
404            memoryName = _memoryHandle.getName();
405        } else {
406            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
407        }
408
409        String message;
410        String other;
411        switch (_compareTo) {
412            case Value:
413                message = "LocalVariable_Long_CompareConstant";
414                other = _constantValue;
415                break;
416
417            case Memory:
418                message = "LocalVariable_Long_CompareMemory";
419                other = memoryName;
420                break;
421
422            case LocalVariable:
423                message = "LocalVariable_Long_CompareLocalVariable";
424                other = _otherLocalVariable;
425                break;
426
427            case RegEx:
428                message = "LocalVariable_Long_CompareRegEx";
429                other = _regEx;
430                break;
431
432            default:
433                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
434        }
435
436        switch (_variableOperation) {
437            case LessThan:
438                // fall through
439            case LessThanOrEqual:
440                // fall through
441            case Equal:
442                // fall through
443            case NotEqual:
444                // fall through
445            case GreaterThanOrEqual:
446                // fall through
447            case GreaterThan:
448                return Bundle.getMessage(locale, message, variableName, _variableOperation._text, other);
449
450            case IsNull:
451                // fall through
452            case IsNotNull:
453                return Bundle.getMessage(locale, "LocalVariable_Long_CompareNull", variableName, _variableOperation._text);
454
455            case MatchRegex:
456                // fall through
457            case NotMatchRegex:
458                return Bundle.getMessage(locale, "LocalVariable_Long_CompareRegEx", variableName, _variableOperation._text, other);
459
460            default:
461                throw new IllegalArgumentException("_variableOperation has unknown value: "+_variableOperation.name());
462        }
463    }
464
465    /** {@inheritDoc} */
466    @Override
467    public void setup() {
468        // Do nothing
469    }
470
471    /** {@inheritDoc} */
472    @Override
473    public void registerListenersForThisClass() {
474        if (!_listenersAreRegistered && (_memoryHandle != null)) {
475            if (_listenToMemory) {
476                _memoryHandle.getBean().addPropertyChangeListener("value", this);
477            }
478            _listenersAreRegistered = true;
479        }
480    }
481
482    /** {@inheritDoc} */
483    @Override
484    public void unregisterListenersForThisClass() {
485        if (_listenersAreRegistered) {
486            if (_listenToMemory && (_memoryHandle != null)) {
487                _memoryHandle.getBean().addPropertyChangeListener("value", this);
488            }
489            _listenersAreRegistered = false;
490        }
491    }
492
493    /** {@inheritDoc} */
494    @Override
495    public void propertyChange(PropertyChangeEvent evt) {
496        getConditionalNG().execute();
497    }
498
499    /** {@inheritDoc} */
500    @Override
501    public void disposeMe() {
502    }
503
504
505
506    public enum VariableOperation {
507        LessThan(Bundle.getMessage("LocalVariableOperation_LessThan"), true),
508        LessThanOrEqual(Bundle.getMessage("LocalVariableOperation_LessThanOrEqual"), true),
509        Equal(Bundle.getMessage("LocalVariableOperation_Equal"), true),
510        GreaterThanOrEqual(Bundle.getMessage("LocalVariableOperation_GreaterThanOrEqual"), true),
511        GreaterThan(Bundle.getMessage("LocalVariableOperation_GreaterThan"), true),
512        NotEqual(Bundle.getMessage("LocalVariableOperation_NotEqual"), true),
513        IsNull(Bundle.getMessage("LocalVariableOperation_IsNull"), false),
514        IsNotNull(Bundle.getMessage("LocalVariableOperation_IsNotNull"), false),
515        MatchRegex(Bundle.getMessage("LocalVariableOperation_MatchRegEx"), true),
516        NotMatchRegex(Bundle.getMessage("LocalVariableOperation_NotMatchRegEx"), true);
517
518        private final String _text;
519        private final boolean _extraValue;
520
521        private VariableOperation(String text, boolean extraValue) {
522            this._text = text;
523            this._extraValue = extraValue;
524        }
525
526        @Override
527        public String toString() {
528            return _text;
529        }
530
531        public boolean hasExtraValue() {
532            return _extraValue;
533        }
534
535    }
536
537
538    public enum CompareTo {
539        Value(Bundle.getMessage("LocalVariable_CompareTo_Value")),
540        Memory(Bundle.getMessage("LocalVariable_CompareTo_Memory")),
541        LocalVariable(Bundle.getMessage("LocalVariable_CompareTo_LocalVariable")),
542        RegEx(Bundle.getMessage("LocalVariable_CompareTo_RegularExpression"));
543
544        private final String _text;
545
546        private CompareTo(String text) {
547            this._text = text;
548        }
549
550        @Override
551        public String toString() {
552            return _text;
553        }
554
555    }
556
557    /** {@inheritDoc} */
558    @Override
559    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
560        log.debug("getUsageReport :: ExpressionLocalVariable: bean = {}, report = {}", cdl, report);
561        if (getMemory() != null && bean.equals(getMemory().getBean())) {
562            report.add(new NamedBeanUsageReport("LogixNGExpression", cdl, getLongDescription()));
563        }
564    }
565
566    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionLocalVariable.class);
567
568}