001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005import java.util.concurrent.atomic.AtomicReference;
006
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.logixng.*;
011import jmri.jmrit.logixng.util.parser.*;
012import jmri.jmrit.logixng.util.parser.ExpressionNode;
013import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
014import jmri.jmrit.logixng.util.LogixNG_SelectTable;
015import jmri.jmrit.logixng.util.ReferenceUtil;
016import jmri.util.ThreadingUtil;
017import jmri.util.TypeConversionUtil;
018
019/**
020 * This action sets the value of a local variable.
021 *
022 * @author Daniel Bergqvist Copyright 2020
023 */
024public class ActionLocalVariable extends AbstractDigitalAction
025        implements PropertyChangeListener {
026
027    private String _localVariable;
028
029    private final LogixNG_SelectNamedBean<Memory> _selectMemoryNamedBean =
030            new LogixNG_SelectNamedBean<>(
031                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
032
033    private final LogixNG_SelectNamedBean<Block> _selectBlockNamedBean =
034            new LogixNG_SelectNamedBean<>(
035                    this, Block.class, InstanceManager.getDefault(BlockManager.class), this);
036
037    private final LogixNG_SelectNamedBean<Reporter> _selectReporterNamedBean =
038            new LogixNG_SelectNamedBean<>(
039                    this, Reporter.class, InstanceManager.getDefault(ReporterManager.class), this);
040
041    private VariableOperation _variableOperation = VariableOperation.SetToString;
042    private ConstantType _constantType = ConstantType.String;
043    private String _constantValue = "";
044    private String _otherLocalVariable = "";
045    private String _reference = "";
046    private String _formula = "";
047    private ExpressionNode _expressionNode;
048    private boolean _listenToMemory = false;
049    private boolean _listenToBlock = false;
050    private boolean _listenToReporter = false;
051
052    private final LogixNG_SelectTable _selectTable =
053            new LogixNG_SelectTable(this, () -> {return _variableOperation == VariableOperation.CopyTableCellToVariable;});
054
055
056    public ActionLocalVariable(String sys, String user)
057            throws BadUserNameException, BadSystemNameException {
058        super(sys, user);
059
060        _selectMemoryNamedBean.setOnlyDirectAddressingAllowed();
061        _selectBlockNamedBean.setOnlyDirectAddressingAllowed();
062        _selectReporterNamedBean.setOnlyDirectAddressingAllowed();
063    }
064
065    @Override
066    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
067        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
068        String sysName = systemNames.get(getSystemName());
069        String userName = userNames.get(getSystemName());
070        if (sysName == null) sysName = manager.getAutoSystemName();
071        ActionLocalVariable copy = new ActionLocalVariable(sysName, userName);
072        copy.setComment(getComment());
073        copy.setLocalVariable(_localVariable);
074        copy.setVariableOperation(_variableOperation);
075        copy.setConstantType(_constantType);
076        copy.setConstantValue(_constantValue);
077        _selectMemoryNamedBean.copy(copy._selectMemoryNamedBean);
078        _selectBlockNamedBean.copy(copy._selectBlockNamedBean);
079        _selectReporterNamedBean.copy(copy._selectReporterNamedBean);
080        copy.setOtherLocalVariable(_otherLocalVariable);
081        copy.setReference(_reference);
082        copy.setFormula(_formula);
083        _selectTable.copy(copy._selectTable);
084        copy.setListenToMemory(_listenToMemory);
085        copy.setListenToBlock(_listenToBlock);
086        copy.setListenToReporter(_listenToReporter);
087        return manager.registerAction(copy);
088    }
089
090    public void setLocalVariable(String variableName) {
091        assertListenersAreNotRegistered(log, "setLocalVariable");   // No I18N
092        _localVariable = variableName;
093    }
094
095    public String getLocalVariable() {
096        return _localVariable;
097    }
098
099    public LogixNG_SelectNamedBean<Memory> getSelectMemoryNamedBean() {
100        return _selectMemoryNamedBean;
101    }
102
103    public LogixNG_SelectNamedBean<Block> getSelectBlockNamedBean() {
104        return _selectBlockNamedBean;
105    }
106
107    public LogixNG_SelectNamedBean<Reporter> getSelectReporterNamedBean() {
108        return _selectReporterNamedBean;
109    }
110
111    public void setVariableOperation(VariableOperation variableOperation) throws ParserException {
112        _variableOperation = variableOperation;
113        parseFormula();
114    }
115
116    public VariableOperation getVariableOperation() {
117        return _variableOperation;
118    }
119
120    public LogixNG_SelectTable getSelectTable() {
121        return _selectTable;
122    }
123
124    public void setOtherLocalVariable(@Nonnull String localVariable) {
125        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
126        _otherLocalVariable = localVariable;
127    }
128
129    public String getOtherLocalVariable() {
130        return _otherLocalVariable;
131    }
132
133    public void setReference(@Nonnull String reference) {
134        assertListenersAreNotRegistered(log, "setReference");
135        _reference = reference;
136    }
137
138    public String getReference() {
139        return _reference;
140    }
141
142    public void setConstantType(ConstantType constantType) {
143        _constantType = constantType;
144    }
145
146    public ConstantType getConstantType() {
147        return _constantType;
148    }
149
150    public void setConstantValue(String constantValue) {
151        _constantValue = constantValue;
152    }
153
154    public String getConstantValue() {
155        return _constantValue;
156    }
157
158    public void setFormula(String formula) throws ParserException {
159        _formula = formula;
160        parseFormula();
161    }
162
163    public String getFormula() {
164        return _formula;
165    }
166
167    public void setListenToMemory(boolean listenToMemory) {
168        this._listenToMemory = listenToMemory;
169    }
170
171    public boolean getListenToMemory() {
172        return _listenToMemory;
173    }
174
175    public void setListenToBlock(boolean listenToBlock) {
176        this._listenToBlock = listenToBlock;
177    }
178
179    public boolean getListenToBlock() {
180        return _listenToBlock;
181    }
182
183    public void setListenToReporter(boolean listenToReporter) {
184        this._listenToReporter = listenToReporter;
185    }
186
187    public boolean getListenToReporter() {
188        return _listenToReporter;
189    }
190
191    private void parseFormula() throws ParserException {
192        if (_variableOperation == VariableOperation.CalculateFormula) {
193            Map<String, Variable> variables = new HashMap<>();
194
195            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
196            _expressionNode = parser.parseExpression(_formula);
197        } else {
198            _expressionNode = null;
199        }
200    }
201
202    /** {@inheritDoc} */
203    @Override
204    public Category getCategory() {
205        return Category.ITEM;
206    }
207
208    /** {@inheritDoc} */
209    @Override
210    public void execute() throws JmriException {
211        if (_localVariable == null) return;
212
213        final ConditionalNG conditionalNG = getConditionalNG();
214
215        SymbolTable symbolTable = conditionalNG.getSymbolTable();
216
217        AtomicReference<JmriException> ref = new AtomicReference<>();
218
219        ThreadingUtil.runOnLayoutWithJmriException(() -> {
220
221            switch (_variableOperation) {
222                case SetToNull:
223                    symbolTable.setValue(_localVariable, null);
224                    break;
225
226                case SetToString: {
227                    Object value;
228                    switch (_constantType) {
229                        case String:
230                            value = _constantValue;
231                            break;
232                        case Integer:
233                            value = TypeConversionUtil.convertToLong(_constantValue);
234                            break;
235                        case FloatingNumber:
236                            value = TypeConversionUtil.convertToDouble(_constantValue, true, true, true);
237                            break;
238                        case Boolean:
239                            value = TypeConversionUtil.convertToBoolean(_constantValue, true);
240                            break;
241                        default:
242                            // Throw exception
243                            throw new IllegalArgumentException("_constantType has invalid value: {}" + _constantType.name());
244                    }
245                    symbolTable.setValue(_localVariable, value);
246                    break;
247                }
248
249                case CopyVariableToVariable:
250                    Object variableValue = conditionalNG
251                                    .getSymbolTable().getValue(_otherLocalVariable);
252
253                    symbolTable.setValue(_localVariable, variableValue);
254                    break;
255
256                case CopyMemoryToVariable:
257                    Memory memory = _selectMemoryNamedBean.evaluateNamedBean(conditionalNG);
258                    if (memory != null) {
259                        symbolTable.setValue(_localVariable, memory.getValue());
260                    } else {
261                        log.warn("ActionLocalVariable should copy memory to variable but memory is null");
262                    }
263                    break;
264
265                case CopyReferenceToVariable:
266                    symbolTable.setValue(_localVariable, ReferenceUtil.getReference(
267                            conditionalNG.getSymbolTable(), _reference));
268                    break;
269
270                case CopyTableCellToVariable:
271                    Object value = _selectTable.evaluateTableData(conditionalNG);
272                    symbolTable.setValue(_localVariable, value);
273                    break;
274
275                case CopyBlockToVariable:
276                    Block block = _selectBlockNamedBean.evaluateNamedBean(conditionalNG);
277                    if (block != null) {
278                        symbolTable.setValue(_localVariable, block.getValue());
279                    } else {
280                        log.warn("ActionLocalVariable should copy block value to variable but block is null");
281                    }
282                    break;
283
284                case CopyReporterToVariable:
285                    Reporter reporter = _selectReporterNamedBean.evaluateNamedBean(conditionalNG);
286                    if (reporter != null) {
287                        symbolTable.setValue(_localVariable, reporter.getCurrentReport());
288                    } else {
289                        log.warn("ActionLocalVariable should copy current report to variable but reporter is null");
290                    }
291                    break;
292
293                case CalculateFormula:
294                    if (_formula.isEmpty()) {
295                        symbolTable.setValue(_localVariable, null);
296                    } else {
297                        if (_expressionNode == null) return;
298
299                        symbolTable.setValue(_localVariable,
300                                _expressionNode.calculate(
301                                        conditionalNG.getSymbolTable()));
302                    }
303                    break;
304
305                default:
306                    // Throw exception
307                    throw new IllegalArgumentException("_variableOperation has invalid value: {}" + _variableOperation.name());
308            }
309        });
310
311        if (ref.get() != null) throw ref.get();
312    }
313
314    @Override
315    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
316        throw new UnsupportedOperationException("Not supported.");
317    }
318
319    @Override
320    public int getChildCount() {
321        return 0;
322    }
323
324    @Override
325    public String getShortDescription(Locale locale) {
326        return Bundle.getMessage(locale, "ActionLocalVariable_Short");
327    }
328
329    @Override
330    public String getLongDescription(Locale locale) {
331        String copyToMemoryName = _selectMemoryNamedBean.getDescription(locale);
332        String copyToBlockName = _selectBlockNamedBean.getDescription(locale);
333        String copyToReporterName = _selectReporterNamedBean.getDescription(locale);
334
335        switch (_variableOperation) {
336            case SetToNull:
337                return Bundle.getMessage(locale, "ActionLocalVariable_Long_Null", _localVariable);
338
339            case SetToString:
340                return Bundle.getMessage(locale, "ActionLocalVariable_Long_Value",
341                        _localVariable, _constantType._text, _constantValue);
342
343            case CopyVariableToVariable:
344                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyVariableToVariable",
345                        _localVariable, _otherLocalVariable);
346
347            case CopyMemoryToVariable:
348                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyMemoryToVariable",
349                        _localVariable, copyToMemoryName, Base.getListenString(_listenToMemory));
350
351            case CopyReferenceToVariable:
352                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyReferenceToVariable",
353                        _localVariable, _reference);
354
355            case CopyBlockToVariable:
356                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyBlockToVariable",
357                        _localVariable, copyToBlockName, Base.getListenString(_listenToBlock));
358
359            case CopyTableCellToVariable:
360                String tableName = _selectTable.getTableNameDescription(locale);
361                String rowName = _selectTable.getTableRowDescription(locale);
362                String columnName = _selectTable.getTableColumnDescription(locale);
363                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyTableCellToVariable", _localVariable, tableName, rowName, columnName);
364
365            case CopyReporterToVariable:
366                return Bundle.getMessage(locale, "ActionLocalVariable_Long_CopyReporterToVariable",
367                        _localVariable, copyToReporterName, Base.getListenString(_listenToReporter));
368
369            case CalculateFormula:
370                return Bundle.getMessage(locale, "ActionLocalVariable_Long_Formula", _localVariable, _formula);
371
372            default:
373                throw new IllegalArgumentException("_variableOperation has invalid value: " + _variableOperation.name());
374        }
375    }
376
377    /** {@inheritDoc} */
378    @Override
379    public void setup() {
380        // Do nothing
381    }
382
383    /** {@inheritDoc} */
384    @Override
385    public void registerListenersForThisClass() {
386        if (!_listenersAreRegistered) {
387            if (_listenToMemory
388                    && (_variableOperation == VariableOperation.CopyMemoryToVariable)) {
389                _selectMemoryNamedBean.addPropertyChangeListener("value", this);
390            }
391            if (_listenToBlock
392                    && (_variableOperation == VariableOperation.CopyBlockToVariable)) {
393                _selectBlockNamedBean.addPropertyChangeListener("value", this);
394            }
395            if (_listenToReporter
396                    && (_variableOperation == VariableOperation.CopyReporterToVariable)) {
397                _selectReporterNamedBean.addPropertyChangeListener("currentReport", this);
398            }
399            _selectMemoryNamedBean.registerListeners();
400            _selectBlockNamedBean.registerListeners();
401            _selectReporterNamedBean.registerListeners();
402            _listenersAreRegistered = true;
403        }
404    }
405
406    /** {@inheritDoc} */
407    @Override
408    public void unregisterListenersForThisClass() {
409        if (_listenersAreRegistered) {
410            if (_listenToMemory
411                    && (_variableOperation == VariableOperation.CopyMemoryToVariable)) {
412                _selectMemoryNamedBean.removePropertyChangeListener("value", this);
413            }
414            if (_listenToBlock
415                    && (_variableOperation == VariableOperation.CopyBlockToVariable)) {
416                _selectBlockNamedBean.removePropertyChangeListener("value", this);
417            }
418            if (_listenToReporter
419                    && (_variableOperation == VariableOperation.CopyReporterToVariable)) {
420                _selectReporterNamedBean.removePropertyChangeListener("currentReport", this);
421            }
422            _selectMemoryNamedBean.unregisterListeners();
423            _selectBlockNamedBean.unregisterListeners();
424            _selectReporterNamedBean.unregisterListeners();
425            _listenersAreRegistered = false;
426        }
427    }
428
429    /** {@inheritDoc} */
430    @Override
431    public void propertyChange(PropertyChangeEvent evt) {
432        getConditionalNG().execute();
433    }
434
435    /** {@inheritDoc} */
436    @Override
437    public void disposeMe() {
438    }
439
440
441    public enum VariableOperation {
442        SetToNull(Bundle.getMessage("ActionLocalVariable_VariableOperation_SetToNull")),
443        SetToString(Bundle.getMessage("ActionLocalVariable_VariableOperation_SetToString")),
444        CopyVariableToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyVariableToVariable")),
445        CopyMemoryToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyMemoryToVariable")),
446        CopyReferenceToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyReferenceToVariable")),
447        CopyTableCellToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyTableCellToVariable")),
448        CopyBlockToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyBlockToVariable")),
449        CopyReporterToVariable(Bundle.getMessage("ActionLocalVariable_VariableOperation_CopyReporterToVariable")),
450        CalculateFormula(Bundle.getMessage("ActionLocalVariable_VariableOperation_CalculateFormula"));
451
452        private final String _text;
453
454        private VariableOperation(String text) {
455            this._text = text;
456        }
457
458        @Override
459        public String toString() {
460            return _text;
461        }
462
463    }
464
465    public enum ConstantType {
466        String(Bundle.getMessage("ActionLocalVariable_ConstantType_String")),
467        Integer(Bundle.getMessage("ActionLocalVariable_ConstantType_Integer")),
468        FloatingNumber(Bundle.getMessage("ActionLocalVariable_ConstantType_FloatingNumber")),
469        Boolean(Bundle.getMessage("ActionLocalVariable_ConstantType_Boolean"));
470
471        private final String _text;
472
473        private ConstantType(String text) {
474            this._text = text;
475        }
476
477        @Override
478        public String toString() {
479            return _text;
480        }
481
482    }
483
484    /** {@inheritDoc} */
485    @Override
486    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
487        log.debug("getUsageReport :: ActionLocalVariable: bean = {}, report = {}", cdl, report);
488        _selectMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
489        _selectBlockNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
490        _selectReporterNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
491    }
492
493    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionLocalVariable.class);
494
495}