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