001package jmri.jmrit.logixng.expressions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.util.*;
006import java.util.regex.Matcher;
007import java.util.regex.Pattern;
008
009import javax.annotation.Nonnull;
010
011import jmri.*;
012import jmri.jmrit.logixng.*;
013import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
014import jmri.jmrit.logixng.util.LogixNG_SelectTable;
015import jmri.util.CompareUtil;
016import jmri.util.CompareUtil.CompareType;
017import jmri.util.CompareUtil.CompareOperation;
018import jmri.util.TypeConversionUtil;
019
020/**
021 * Evaluates the state of a local variable.
022 *
023 * @author Daniel Bergqvist Copyright 2020
024 */
025public class ExpressionLocalVariable extends AbstractDigitalExpression
026        implements PropertyChangeListener {
027
028    private String _localVariable;
029    private VariableOperation _variableOperation = VariableOperation.Equal;
030    private CompareType _compareType = CompareType.NumberOrString;
031    private CompareTo _compareTo = CompareTo.Value;
032    private boolean _caseInsensitive = false;
033    private String _constantValue = "";
034    private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean =
035            new LogixNG_SelectNamedBean<>(
036                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
037    private String _otherLocalVariable = "";
038    private String _regEx = "";
039    private boolean _listenToMemory = true;
040
041    private final LogixNG_SelectTable _selectTable =
042            new LogixNG_SelectTable(this, () -> {return _compareTo == CompareTo.Table;});
043
044
045    public ExpressionLocalVariable(String sys, String user)
046            throws BadUserNameException, BadSystemNameException {
047        super(sys, user);
048    }
049
050    @Override
051    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
052        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
053        String sysName = systemNames.get(getSystemName());
054        String userName = userNames.get(getSystemName());
055        if (sysName == null) sysName = manager.getAutoSystemName();
056        ExpressionLocalVariable copy = new ExpressionLocalVariable(sysName, userName);
057        copy.setComment(getComment());
058        copy.setLocalVariable(_localVariable);
059        copy.setVariableOperation(_variableOperation);
060        copy.setCompareType(_compareType);
061        copy.setCompareTo(_compareTo);
062        copy.setCaseInsensitive(_caseInsensitive);
063        copy.setConstantValue(_constantValue);
064        _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean);
065        copy.setOtherLocalVariable(_otherLocalVariable);
066        copy.setRegEx(_regEx);
067        _selectTable.copy(copy._selectTable);
068        return manager.registerExpression(copy).deepCopyChildren(this, systemNames, userNames);
069    }
070
071    public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() {
072        return _selectMemoryNamedBean;
073    }
074
075    public void setLocalVariable(String variableName) {
076        assertListenersAreNotRegistered(log, "setLocalVariable");
077        _localVariable = variableName;
078    }
079
080    public String getLocalVariable() {
081        return _localVariable;
082    }
083
084    public void setOtherLocalVariable(@Nonnull String localVariable) {
085        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
086        _otherLocalVariable = localVariable;
087    }
088
089    public String getOtherLocalVariable() {
090        return _otherLocalVariable;
091    }
092
093    public LogixNG_SelectTable getSelectTable() {
094        return _selectTable;
095    }
096
097    public void setConstantValue(String constantValue) {
098        _constantValue = constantValue;
099    }
100
101    public String getConstantValue() {
102        return _constantValue;
103    }
104
105    public void setRegEx(String regEx) {
106        _regEx = regEx;
107    }
108
109    public String getRegEx() {
110        return _regEx;
111    }
112
113    public void setListenToMemory(boolean listenToMemory) {
114        this._listenToMemory = listenToMemory;
115    }
116
117    public boolean getListenToMemory() {
118        return _listenToMemory;
119    }
120
121    public void setVariableOperation(VariableOperation variableOperation) {
122        _variableOperation = variableOperation;
123    }
124
125    public VariableOperation getVariableOperation() {
126        return _variableOperation;
127    }
128
129    public void setCompareType(CompareType compareType) {
130        _compareType = compareType;
131    }
132
133    public CompareType getCompareType() {
134        return _compareType;
135    }
136
137    public void setCompareTo(CompareTo compareTo) {
138        _compareTo = compareTo;
139    }
140
141    public CompareTo getCompareTo() {
142        return _compareTo;
143    }
144
145    public void setCaseInsensitive(boolean caseInsensitive) {
146        _caseInsensitive = caseInsensitive;
147    }
148
149    public boolean getCaseInsensitive() {
150        return _caseInsensitive;
151    }
152
153    /** {@inheritDoc} */
154    @Override
155    public Category getCategory() {
156        return Category.ITEM;
157    }
158
159    private String getString(Object o) {
160        if (o != null) {
161            return o.toString();
162        }
163        return null;
164    }
165
166    private boolean matchRegex(String memoryValue, String regex) {
167        Pattern pattern = Pattern.compile(regex);
168        Matcher m = pattern.matcher(memoryValue);
169        return m.matches();
170    }
171
172    /** {@inheritDoc} */
173    @Override
174    public boolean evaluate() throws JmriException {
175        if (_localVariable == null) return false;
176
177        String variableValue = getString(getConditionalNG()
178                        .getSymbolTable().getValue(_localVariable));
179        String otherValue = null;
180        boolean result;
181
182        switch (_compareTo) {
183            case Value:
184                otherValue = _constantValue;
185                break;
186            case Memory:
187                Memory memory = _selectMemoryNamedBean.evaluateNamedBean(getConditionalNG());
188                otherValue = getString(memory.getValue());
189                break;
190            case Table:
191                otherValue = getString(_selectTable.evaluateTableData(getConditionalNG()));
192                break;
193            case LocalVariable:
194                otherValue = TypeConversionUtil.convertToString(getConditionalNG().getSymbolTable().getValue(_otherLocalVariable), false);
195                break;
196            case RegEx:
197                // Do nothing
198                break;
199            default:
200                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
201        }
202
203        switch (_variableOperation) {
204            case LessThan:
205                // fall through
206            case LessThanOrEqual:
207                // fall through
208            case Equal:
209                // fall through
210            case NotEqual:
211                // fall through
212            case GreaterThanOrEqual:
213                // fall through
214            case GreaterThan:
215                result = CompareUtil.compare(_compareType, _variableOperation._oper, variableValue, otherValue, _caseInsensitive);
216                break;
217
218            case IsNull:
219                result = variableValue == null;
220                break;
221            case IsNotNull:
222                result = variableValue != null;
223                break;
224
225            case MatchRegex:
226                result = matchRegex(variableValue, _regEx);
227                break;
228
229            case NotMatchRegex:
230                result = !matchRegex(variableValue, _regEx);
231                break;
232
233            default:
234                throw new IllegalArgumentException("_memoryOperation has unknown value: "+_variableOperation.name());
235        }
236
237        return result;
238    }
239
240    @Override
241    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
242        throw new UnsupportedOperationException("Not supported.");
243    }
244
245    @Override
246    public int getChildCount() {
247        return 0;
248    }
249
250    @Override
251    public String getShortDescription(Locale locale) {
252        return Bundle.getMessage(locale, "LocalVariable_Short");
253    }
254
255    @Override
256    public String getLongDescription(Locale locale) {
257        String variableName;
258        if ((_localVariable == null) || _localVariable.isEmpty()) {
259            variableName = Bundle.getMessage(locale, "BeanNotSelected");
260        } else {
261            variableName = _localVariable;
262        }
263
264        String memoryName = _selectMemoryNamedBean.getDescription(locale);
265
266        String message;
267        String other1;
268        String other2 = null;
269        String other3 = null;
270
271        switch (_compareTo) {
272            case Value:
273                message = "LocalVariable_Long_CompareConstant";
274                other1 = _constantValue;
275                break;
276
277            case Memory:
278                message = "LocalVariable_Long_CompareMemory";
279                other1 = memoryName;
280                break;
281
282            case LocalVariable:
283                message = "LocalVariable_Long_CompareLocalVariable";
284                other1 = _otherLocalVariable;
285                break;
286
287            case Table:
288                message = "LocalVariable_Long_CompareTable";
289                other1 = _selectTable.getTableNameDescription(locale);
290                other2 = _selectTable.getTableRowDescription(locale);
291                other3 = _selectTable.getTableColumnDescription(locale);
292                break;
293
294            case RegEx:
295                message = "LocalVariable_Long_CompareRegEx";
296                other1 = _regEx;
297                break;
298
299            default:
300                throw new IllegalArgumentException("_compareTo has unknown value: "+_compareTo.name());
301        }
302
303        switch (_variableOperation) {
304            case LessThan:
305                // fall through
306            case LessThanOrEqual:
307                // fall through
308            case Equal:
309                // fall through
310            case NotEqual:
311                // fall through
312            case GreaterThanOrEqual:
313                // fall through
314            case GreaterThan:
315                return Bundle.getMessage(locale, message, variableName, _variableOperation._text, other1, other2, other3);
316
317            case IsNull:
318                // fall through
319            case IsNotNull:
320                return Bundle.getMessage(locale, "LocalVariable_Long_CompareNull", variableName, _variableOperation._text);
321
322            case MatchRegex:
323                // fall through
324            case NotMatchRegex:
325                return Bundle.getMessage(locale, "LocalVariable_Long_CompareRegEx", variableName, _variableOperation._text, other1);
326
327            default:
328                throw new IllegalArgumentException("_variableOperation has unknown value: "+_variableOperation.name());
329        }
330    }
331
332    /** {@inheritDoc} */
333    @Override
334    public void setup() {
335        // Do nothing
336    }
337
338    /** {@inheritDoc} */
339    @Override
340    public void registerListenersForThisClass() {
341        if (!_listenersAreRegistered && _listenToMemory) {
342            _selectMemoryNamedBean.addPropertyChangeListener("value", this);
343            _selectMemoryNamedBean.registerListeners();
344            _listenersAreRegistered = true;
345        }
346    }
347
348    /** {@inheritDoc} */
349    @Override
350    public void unregisterListenersForThisClass() {
351        if (_listenersAreRegistered && _listenToMemory) {
352            _selectMemoryNamedBean.removePropertyChangeListener("value", this);
353            _selectMemoryNamedBean.unregisterListeners();
354            _listenersAreRegistered = false;
355        }
356    }
357
358    /** {@inheritDoc} */
359    @Override
360    public void propertyChange(PropertyChangeEvent evt) {
361        getConditionalNG().execute();
362    }
363
364    /** {@inheritDoc} */
365    @Override
366    public void disposeMe() {
367    }
368
369
370    public enum VariableOperation {
371        LessThan(CompareOperation.LessThan, null, true),
372        LessThanOrEqual(CompareOperation.LessThanOrEqual, null, true),
373        Equal(CompareOperation.Equal, null, true),
374        GreaterThanOrEqual(CompareOperation.GreaterThanOrEqual, null, true),
375        GreaterThan(CompareOperation.GreaterThan, null, true),
376        NotEqual(CompareOperation.NotEqual, null, true),
377        IsNull(null, Bundle.getMessage("LocalVariableOperation_IsNull"), false),
378        IsNotNull(null, Bundle.getMessage("LocalVariableOperation_IsNotNull"), false),
379        MatchRegex(null, Bundle.getMessage("LocalVariableOperation_MatchRegEx"), true),
380        NotMatchRegex(null, Bundle.getMessage("LocalVariableOperation_NotMatchRegEx"), true);
381
382        private final CompareOperation _oper;
383        private final String _text;
384        private final boolean _extraValue;
385
386        private VariableOperation(CompareOperation oper, String text, boolean extraValue) {
387            this._oper = oper;
388            this._text = oper != null ? oper.toString() : text;
389            this._extraValue = extraValue;
390        }
391
392        @Override
393        public String toString() {
394            return _text;
395        }
396
397        public boolean hasExtraValue() {
398            return _extraValue;
399        }
400
401    }
402
403
404    public enum CompareTo {
405        Value(Bundle.getMessage("LocalVariable_CompareTo_Value")),
406        Memory(Bundle.getMessage("LocalVariable_CompareTo_Memory")),
407        LocalVariable(Bundle.getMessage("LocalVariable_CompareTo_LocalVariable")),
408        Table(Bundle.getMessage("LocalVariable_CompareTo_Table")),
409        RegEx(Bundle.getMessage("LocalVariable_CompareTo_RegularExpression"));
410
411        private final String _text;
412
413        private CompareTo(String text) {
414            this._text = text;
415        }
416
417        @Override
418        public String toString() {
419            return _text;
420        }
421
422    }
423
424    /** {@inheritDoc} */
425    @Override
426    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
427        log.debug("getUsageReport :: ExpressionLocalVariable: bean = {}, report = {}", cdl, report);
428        _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression);
429    }
430
431    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionLocalVariable.class);
432
433}