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 Reporter.
020 *
021 * @author Daniel Bergqvist Copyright 2018
022 * @author Dave Sand Copyright 2021
023 */
024public class ExpressionReporter extends AbstractDigitalExpression
025        implements PropertyChangeListener, VetoableChangeListener {
026
027    private NamedBeanHandle<Reporter> _reporterHandle;
028
029    private ReporterValue _reporterValue = ReporterValue.CurrentReport;
030    private ReporterOperation _reporterOperation = ReporterOperation.Equal;
031    private CompareTo _compareTo = CompareTo.Value;
032
033    private boolean _caseInsensitive = false;
034    private String _constantValue = "";
035    private NamedBeanHandle<Memory> _memoryHandle;
036    private String _localVariable = "";
037    private String _regEx = "";
038    private boolean _listenToMemory = true;
039//    private boolean _listenToMemory = false;
040
041    public ExpressionReporter(String sys, String user)
042            throws BadUserNameException, BadSystemNameException {
043        super(sys, user);
044    }
045
046    @Override
047    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
048        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
049        String sysName = systemNames.get(getSystemName());
050        String userName = userNames.get(getSystemName());
051        if (sysName == null) sysName = manager.getAutoSystemName();
052        ExpressionReporter copy = new ExpressionReporter(sysName, userName);
053        copy.setComment(getComment());
054        if (_reporterHandle != null) copy.setReporter(_reporterHandle);
055        copy.setReporterValue(_reporterValue);
056        copy.setReporterOperation(_reporterOperation);
057        copy.setCompareTo(_compareTo);
058        copy.setCaseInsensitive(_caseInsensitive);
059        copy.setConstantValue(_constantValue);
060        if (_memoryHandle != null) copy.setMemory(_memoryHandle);
061        copy.setListenToMemory(_listenToMemory);
062        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
063    }
064
065
066    public void setReporter(@Nonnull String reporterName) {
067        assertListenersAreNotRegistered(log, "setReporter");
068        Reporter reporter = InstanceManager.getDefault(ReporterManager.class).getReporter(reporterName);
069        if (reporter != null) {
070            setReporter(reporter);
071        } else {
072            removeReporter();
073            log.warn("reporter \"{}\" is not found", reporterName);
074        }
075    }
076
077    public void setReporter(@Nonnull NamedBeanHandle<Reporter> handle) {
078        assertListenersAreNotRegistered(log, "setReporter");
079        _reporterHandle = handle;
080        InstanceManager.getDefault(ReporterManager.class).addVetoableChangeListener(this);
081    }
082
083    public void setReporter(@Nonnull Reporter reporter) {
084        assertListenersAreNotRegistered(log, "setReporter");
085        setReporter(InstanceManager.getDefault(NamedBeanHandleManager.class)
086                .getNamedBeanHandle(reporter.getDisplayName(), reporter));
087    }
088
089    public void removeReporter() {
090        assertListenersAreNotRegistered(log, "removeReporter");
091        if (_reporterHandle != null) {
092            InstanceManager.getDefault(ReporterManager.class).removeVetoableChangeListener(this);
093            _reporterHandle = null;
094        }
095    }
096
097    public NamedBeanHandle<Reporter> getReporter() {
098        return _reporterHandle;
099    }
100
101
102    public void setMemory(@Nonnull String memoryName) {
103        assertListenersAreNotRegistered(log, "setMemory");
104        MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class);
105        Memory memory = memoryManager.getMemory(memoryName);
106        if (memory != null) {
107            setMemory(memory);
108        } else {
109            removeMemory();
110            log.warn("memory \"{}\" is not found", memoryName);
111        }
112    }
113
114    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
115        assertListenersAreNotRegistered(log, "setMemory");
116        _memoryHandle = handle;
117        InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
118    }
119
120    public void setMemory(@Nonnull Memory memory) {
121        assertListenersAreNotRegistered(log, "setMemory");
122        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
123                .getNamedBeanHandle(memory.getDisplayName(), memory));
124    }
125
126    public void removeMemory() {
127        assertListenersAreNotRegistered(log, "removeMemory");
128        if (_memoryHandle != null) {
129            _memoryHandle = null;
130            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
131        }
132    }
133
134    public NamedBeanHandle<Memory> getMemory() {
135        return _memoryHandle;
136    }
137
138
139    public void setLocalVariable(@Nonnull String localVariable) {
140        assertListenersAreNotRegistered(log, "setLocalVariable");
141        _localVariable = localVariable;
142    }
143
144    public String getLocalVariable() {
145        return _localVariable;
146    }
147
148
149    public void setConstantValue(String constantValue) {
150        _constantValue = constantValue;
151    }
152
153    public String getConstantValue() {
154        return _constantValue;
155    }
156
157
158    public void setRegEx(String regEx) {
159        _regEx = regEx;
160    }
161
162    public String getRegEx() {
163        return _regEx;
164    }
165
166
167    public void setListenToMemory(boolean listenToMemory) {
168        this._listenToMemory = listenToMemory;
169    }
170
171    public boolean getListenToMemory() {
172        return _listenToMemory;
173    }
174
175
176    public void setReporterValue(ReporterValue reporterValue) {
177        _reporterValue = reporterValue;
178    }
179
180    public ReporterValue getReporterValue() {
181        return _reporterValue;
182    }
183
184
185    public void setReporterOperation(ReporterOperation reporterOperation) {
186        _reporterOperation = reporterOperation;
187    }
188
189    public ReporterOperation getReporterOperation() {
190        return _reporterOperation;
191    }
192
193
194    public void setCompareTo(CompareTo compareTo) {
195        _compareTo = compareTo;
196    }
197
198    public CompareTo getCompareTo() {
199        return _compareTo;
200    }
201
202
203    public void setCaseInsensitive(boolean caseInsensitive) {
204        _caseInsensitive = caseInsensitive;
205    }
206
207    public boolean getCaseInsensitive() {
208        return _caseInsensitive;
209    }
210
211
212    @Override
213    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
214        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
215            if (evt.getOldValue() instanceof Reporter) {
216                if (evt.getOldValue().equals(getReporter().getBean())) {
217                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
218                    throw new PropertyVetoException(Bundle.getMessage("Reporter_ReporterInUseVeto", getDisplayName()), e); // NOI18N
219                }
220            }
221
222            if (evt.getOldValue() instanceof Memory) {
223                if (evt.getOldValue().equals(getMemory().getBean())) {
224                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
225                    throw new PropertyVetoException(Bundle.getMessage("Reporter_MemoryInUseVeto", getDisplayName()), e); // NOI18N
226                }
227            }
228        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
229            if (evt.getOldValue() instanceof Reporter) {
230                if (evt.getOldValue().equals(getReporter().getBean())) {
231                    removeReporter();
232                }
233            }
234
235            if (evt.getOldValue() instanceof Memory) {
236                if (evt.getOldValue().equals(getMemory().getBean())) {
237                    removeMemory();
238                }
239            }
240        }
241    }
242
243    /** {@inheritDoc} */
244    @Override
245    public Category getCategory() {
246        return Category.ITEM;
247    }
248
249    private String getString(Object o) {
250        if (o != null) {
251            return o.toString();
252        }
253        return null;
254    }
255
256    /**
257     * Compare two values using the comparator set using the comparison
258     * instructions in {@link #_reporterOperation}.
259     *
260     * <strong>Note:</strong> {@link #_reporterOperation} must be one of
261     * {@link #ExpressionReporter.ReporterOperation.LESS_THAN},
262     * {@link #ExpressionReporter.ReporterOperation.LESS_THAN_OR_EQUAL},
263     * {@link #ExpressionReporter.ReporterOperation.EQUAL},
264     * {@link #ExpressionReporter.ReporterOperation.GREATER_THAN_OR_EQUAL},
265     * or {@link #ExpressionReporter.ReporterOperation.GREATER_THAN}.
266     *
267     * @param value1          left side of the comparison
268     * @param value2          right side of the comparison
269     * @param caseInsensitive true if comparison should be case insensitive;
270     *                        false otherwise
271     * @return true if values compare per _reporterOperation; false otherwise
272     */
273    private boolean compare(String value1, String value2, boolean caseInsensitive) {
274        if (value1 == null) {
275            return value2 == null;
276        } else {
277            if (value2 == null) {
278                return false;
279            }
280            value1 = value1.trim();
281            value2 = value2.trim();
282        }
283        try {
284            int n1 = Integer.parseInt(value1);
285            try {
286                int n2 = Integer.parseInt(value2);
287                log.debug("Compare numbers: n1= {} to n2= {}", n1, n2);
288                switch (_reporterOperation) // both are numbers
289                {
290                    case LessThan:
291                        return (n1 < n2);
292                    case LessThanOrEqual:
293                        return (n1 <= n2);
294                    case Equal:
295                        return (n1 == n2);
296                    case NotEqual:
297                        return (n1 != n2);
298                    case GreaterThanOrEqual:
299                        return (n1 >= n2);
300                    case GreaterThan:
301                        return (n1 > n2);
302                    default:
303                        throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name());
304                }
305            } catch (NumberFormatException nfe) {
306                return _reporterOperation == ReporterOperation.NotEqual;   // n1 is a number, n2 is not
307            }
308        } catch (NumberFormatException nfe) {
309            try {
310                Integer.parseInt(value2);
311                return _reporterOperation == ReporterOperation.NotEqual;     // n1 is not a number, n2 is
312            } catch (NumberFormatException ex) { // OK neither a number
313            }
314        }
315        log.debug("Compare Strings: value1= {} to value2= {}", value1, value2);
316        int compare;
317        if (caseInsensitive) {
318            compare = value1.compareToIgnoreCase(value2);
319        } else {
320            compare = value1.compareTo(value2);
321        }
322        switch (_reporterOperation) {
323            case LessThan:
324                if (compare < 0) {
325                    return true;
326                }
327                break;
328            case LessThanOrEqual:
329                if (compare <= 0) {
330                    return true;
331                }
332                break;
333            case Equal:
334                if (compare == 0) {
335                    return true;
336                }
337                break;
338            case NotEqual:
339                if (compare != 0) {
340                    return true;
341                }
342                break;
343            case GreaterThanOrEqual:
344                if (compare >= 0) {
345                    return true;
346                }
347                break;
348            case GreaterThan:
349                if (compare > 0) {
350                    return true;
351                }
352                break;
353            default:
354                throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name());
355        }
356        return false;
357    }
358
359    private boolean matchRegex(String reporterValue, String regex) {
360        Pattern pattern = Pattern.compile(regex);
361        Matcher m = pattern.matcher(reporterValue);
362        return m.matches();
363    }
364
365    /** {@inheritDoc} */
366    @Override
367    public boolean evaluate() {
368        if (_reporterHandle == null) return false;
369
370        Object obj;
371        switch (_reporterValue) {
372            case CurrentReport:
373                obj = _reporterHandle.getBean().getCurrentReport();
374                break;
375
376            case LastReport:
377                obj = _reporterHandle.getBean().getLastReport();
378                break;
379
380            case State:
381                obj = _reporterHandle.getBean().getState();
382                break;
383
384            default:
385                throw new IllegalArgumentException("_reporterValue has unknown value: "+_reporterValue.name());
386        }
387        String reporterValue = getString(obj);
388        String otherValue = null;
389        boolean result;
390
391        switch (_compareTo) {
392            case Value:
393                otherValue = _constantValue;
394                break;
395            case Memory:
396                otherValue = getString(_memoryHandle.getBean().getValue());
397                break;
398            case LocalVariable:
399                otherValue = TypeConversionUtil.convertToString(getConditionalNG().getSymbolTable().getValue(_localVariable), false);
400                break;
401            case RegEx:
402                // Do nothing
403                break;
404            default:
405                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
406        }
407
408        switch (_reporterOperation) {
409            case LessThan:
410                // fall through
411            case LessThanOrEqual:
412                // fall through
413            case Equal:
414                // fall through
415            case NotEqual:
416                // fall through
417            case GreaterThanOrEqual:
418                // fall through
419            case GreaterThan:
420                result = compare(reporterValue, otherValue, _caseInsensitive);
421                break;
422
423            case IsNull:
424                result = reporterValue == null;
425                break;
426
427            case IsNotNull:
428                result = reporterValue != null;
429                break;
430
431            case MatchRegex:
432                result = matchRegex(reporterValue, _regEx);
433                break;
434
435            case NotMatchRegex:
436                result = !matchRegex(reporterValue, _regEx);
437                break;
438
439            default:
440                throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name());
441        }
442
443        return result;
444    }
445
446    @Override
447    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
448        throw new UnsupportedOperationException("Not supported.");
449    }
450
451    @Override
452    public int getChildCount() {
453        return 0;
454    }
455
456    @Override
457    public String getShortDescription(Locale locale) {
458        return Bundle.getMessage(locale, "Reporter_Short");
459    }
460
461    @Override
462    public String getLongDescription(Locale locale) {
463        String reporterName;
464        if (_reporterHandle != null) {
465            reporterName = _reporterHandle.getName();
466        } else {
467            reporterName = Bundle.getMessage(locale, "BeanNotSelected");
468        }
469
470        String memoryName;
471        if (_memoryHandle != null) {
472            memoryName = _memoryHandle.getName();
473        } else {
474            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
475        }
476
477        String message;
478        String other;
479        switch (_compareTo) {
480            case Value:
481                message = "Reporter_Long_CompareConstant";
482                other = _constantValue;
483                break;
484
485            case Memory:
486                message = "Reporter_Long_CompareMemory";
487                other = memoryName;
488                break;
489
490            case LocalVariable:
491                message = "Reporter_Long_CompareLocalVariable";
492                other = _localVariable;
493                break;
494
495            case RegEx:
496                message = "Reporter_Long_CompareRegEx";
497                other = _regEx;
498                break;
499
500            default:
501                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
502        }
503
504        switch (_reporterOperation) {
505            case LessThan:
506                // fall through
507            case LessThanOrEqual:
508                // fall through
509            case Equal:
510                // fall through
511            case NotEqual:
512                // fall through
513            case GreaterThanOrEqual:
514                // fall through
515            case GreaterThan:
516                return Bundle.getMessage(locale, message, reporterName, _reporterValue._text, _reporterOperation._text, other);
517
518            case IsNull:
519                // fall through
520            case IsNotNull:
521                return Bundle.getMessage(locale, "Reporter_Long_CompareNull", reporterName, _reporterValue._text, _reporterOperation._text);
522
523            case MatchRegex:
524                // fall through
525            case NotMatchRegex:
526                return Bundle.getMessage(locale, "Reporter_Long_CompareRegEx", reporterName, _reporterValue._text, _reporterOperation._text, other);
527
528            default:
529                throw new IllegalArgumentException("_reporterOperation has unknown value: "+_reporterOperation.name());
530        }
531    }
532
533    /** {@inheritDoc} */
534    @Override
535    public void setup() {
536        // Do nothing
537    }
538
539    /** {@inheritDoc} */
540    @Override
541    public void registerListenersForThisClass() {
542        if (!_listenersAreRegistered && (_reporterHandle != null)) {
543            switch (_reporterValue) {
544                case CurrentReport:
545                    _reporterHandle.getBean().addPropertyChangeListener("currentReport", this);
546                    break;
547
548                case LastReport:
549                    _reporterHandle.getBean().addPropertyChangeListener("lastReport", this);
550                    break;
551
552                case State:
553                    // No property change event is sent when state is changed for reports
554                    break;
555
556                default:
557                    // Do nothing
558            }
559            if (_listenToMemory && (_memoryHandle != null)) {
560                _memoryHandle.getBean().addPropertyChangeListener("value", this);
561            }
562            _listenersAreRegistered = true;
563        }
564    }
565
566    /** {@inheritDoc} */
567    @Override
568    public void unregisterListenersForThisClass() {
569        if (_listenersAreRegistered) {
570            _reporterHandle.getBean().removePropertyChangeListener("currentReport", this);
571            _reporterHandle.getBean().removePropertyChangeListener("lastReport", this);
572            if (_listenToMemory && (_memoryHandle != null)) {
573                _memoryHandle.getBean().removePropertyChangeListener("value", this);
574            }
575            _listenersAreRegistered = false;
576        }
577    }
578
579    /** {@inheritDoc} */
580    @Override
581    public void propertyChange(PropertyChangeEvent evt) {
582        getConditionalNG().execute();
583    }
584
585    /** {@inheritDoc} */
586    @Override
587    public void disposeMe() {
588    }
589
590    public enum ReporterValue {
591        CurrentReport(Bundle.getMessage("Reporter_Value_CurrentReport")),
592        LastReport(Bundle.getMessage("Reporter_Value_LastReport")),
593        State(Bundle.getMessage("Reporter_Value_State"));
594
595        private final String _text;
596
597        private ReporterValue(String text) {
598            this._text = text;
599        }
600
601        @Override
602        public String toString() {
603            return _text;
604        }
605    }
606
607
608    public enum ReporterOperation {
609        LessThan(Bundle.getMessage("ReporterOperation_LessThan"), true),
610        LessThanOrEqual(Bundle.getMessage("ReporterOperation_LessThanOrEqual"), true),
611        Equal(Bundle.getMessage("ReporterOperation_Equal"), true),
612        GreaterThanOrEqual(Bundle.getMessage("ReporterOperation_GreaterThanOrEqual"), true),
613        GreaterThan(Bundle.getMessage("ReporterOperation_GreaterThan"), true),
614        NotEqual(Bundle.getMessage("ReporterOperation_NotEqual"), true),
615        IsNull(Bundle.getMessage("ReporterOperation_IsNull"), false),
616        IsNotNull(Bundle.getMessage("ReporterOperation_IsNotNull"), false),
617        MatchRegex(Bundle.getMessage("ReporterOperation_MatchRegEx"), true),
618        NotMatchRegex(Bundle.getMessage("ReporterOperation_NotMatchRegEx"), true);
619
620        private final String _text;
621        private final boolean _extraValue;
622
623        private ReporterOperation(String text, boolean extraValue) {
624            this._text = text;
625            this._extraValue = extraValue;
626        }
627
628        @Override
629        public String toString() {
630            return _text;
631        }
632
633        public boolean hasExtraValue() {
634            return _extraValue;
635        }
636
637    }
638
639
640    public enum CompareTo {
641        Value(Bundle.getMessage("Reporter_CompareTo_Value")),
642        Memory(Bundle.getMessage("Reporter_CompareTo_Memory")),
643        LocalVariable(Bundle.getMessage("Reporter_CompareTo_LocalVariable")),
644        RegEx(Bundle.getMessage("Reporter_CompareTo_RegularExpression"));
645
646        private final String _text;
647
648        private CompareTo(String text) {
649            this._text = text;
650        }
651
652        @Override
653        public String toString() {
654            return _text;
655        }
656
657    }
658
659    /** {@inheritDoc} */
660    @Override
661    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
662        log.debug("getUsageReport :: ExpressionReporter: bean = {}, report = {}", cdl, report);
663        if (getReporter() != null && bean.equals(getReporter().getBean())) {
664            report.add(new NamedBeanUsageReport("LogixNGExpression", cdl, getLongDescription()));
665        }
666        if (getMemory() != null && bean.equals(getMemory().getBean())) {
667            report.add(new NamedBeanUsageReport("LogixNGExpression", cdl, getLongDescription()));
668        }
669    }
670
671    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionReporter.class);
672
673}