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.LogixNG_SelectNamedBean;
012import jmri.jmrit.logixng.util.parser.*;
013import jmri.jmrit.logixng.util.parser.ExpressionNode;
014import jmri.jmrit.logixng.util.LogixNG_SelectTable;
015import jmri.util.ThreadingUtil;
016
017/**
018 * This action sets the value of a memory.
019 *
020 * @author Daniel Bergqvist Copyright 2018
021 */
022public class ActionMemory extends AbstractDigitalAction
023        implements PropertyChangeListener {
024
025    private final LogixNG_SelectNamedBean<Memory> _selectNamedBean =
026            new LogixNG_SelectNamedBean<>(
027                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
028    private final LogixNG_SelectNamedBean<Memory> _selectOtherMemoryNamedBean =
029            new LogixNG_SelectNamedBean<>(
030                    this, Memory.class, InstanceManager.getDefault(MemoryManager.class), this);
031//    private NamedBeanHandle<Memory> _otherMemoryHandle;
032    private MemoryOperation _memoryOperation = MemoryOperation.SetToString;
033    private String _otherConstantValue = "";
034    private String _otherLocalVariable = "";
035    private String _otherFormula = "";
036    private ExpressionNode _otherExpressionNode;
037    private boolean _listenToMemory = true;
038
039    private final LogixNG_SelectTable _selectTable =
040            new LogixNG_SelectTable(this, () -> {return _memoryOperation == MemoryOperation.CopyTableCellToMemory;});
041
042
043    public ActionMemory(String sys, String user)
044            throws BadUserNameException, BadSystemNameException {
045        super(sys, user);
046    }
047
048    @Override
049    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
050        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
051        String sysName = systemNames.get(getSystemName());
052        String userName = userNames.get(getSystemName());
053        if (sysName == null) sysName = manager.getAutoSystemName();
054        ActionMemory copy = new ActionMemory(sysName, userName);
055        copy.setComment(getComment());
056        _selectNamedBean.copy(copy._selectNamedBean);
057        _selectOtherMemoryNamedBean.copy(copy._selectOtherMemoryNamedBean);
058        copy.setMemoryOperation(_memoryOperation);
059        copy.setOtherConstantValue(_otherConstantValue);
060//        copy.setOtherTableCell(_otherTableCell);
061        copy.setOtherLocalVariable(_otherLocalVariable);
062        copy.setOtherFormula(_otherFormula);
063        copy.setListenToMemory(_listenToMemory);
064        _selectTable.copy(copy._selectTable);
065        return manager.registerAction(copy);
066    }
067
068    public LogixNG_SelectNamedBean<Memory> getSelectNamedBean() {
069        return _selectNamedBean;
070    }
071
072    public LogixNG_SelectNamedBean<Memory> getSelectOtherMemoryNamedBean() {
073        return _selectOtherMemoryNamedBean;
074    }
075
076    public void setMemoryOperation(MemoryOperation state) throws ParserException {
077        _memoryOperation = state;
078        parseOtherFormula();
079    }
080
081    public MemoryOperation getMemoryOperation() {
082        return _memoryOperation;
083    }
084
085    // Constant tab
086    public void setOtherConstantValue(String constantValue) {
087        _otherConstantValue = constantValue;
088    }
089
090    public String getConstantValue() {
091        return _otherConstantValue;
092    }
093
094    public LogixNG_SelectTable getSelectTable() {
095        return _selectTable;
096    }
097
098    public void setListenToMemory(boolean listenToMemory) {
099        this._listenToMemory = listenToMemory;
100    }
101
102    public boolean getListenToMemory() {
103        return _listenToMemory;
104    }
105
106    // Variable tab
107    public void setOtherLocalVariable(@Nonnull String localVariable) {
108        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
109        _otherLocalVariable = localVariable;
110    }
111
112    public String getOtherLocalVariable() {
113        return _otherLocalVariable;
114    }
115
116    // Formula tab
117    public void setOtherFormula(String formula) throws ParserException {
118        _otherFormula = formula;
119        parseOtherFormula();
120    }
121
122    public String getOtherFormula() {
123        return _otherFormula;
124    }
125
126    private void parseOtherFormula() throws ParserException {
127        if (_memoryOperation == MemoryOperation.CalculateFormula) {
128            Map<String, Variable> variables = new HashMap<>();
129            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
130            _otherExpressionNode = parser.parseExpression(_otherFormula);
131        } else {
132            _otherExpressionNode = null;
133        }
134    }
135
136    /** {@inheritDoc} */
137    @Override
138    public Category getCategory() {
139        return Category.ITEM;
140    }
141
142    /** {@inheritDoc} */
143    @Override
144    public void execute() throws JmriException {
145
146        final ConditionalNG conditionalNG = getConditionalNG();
147
148        Memory memory = _selectNamedBean.evaluateNamedBean(conditionalNG);
149
150        if (memory == null) {
151//            log.warn("memory is null");
152            return;
153        }
154
155        AtomicReference<JmriException> ref = new AtomicReference<>();
156
157        ThreadingUtil.runOnLayoutWithJmriException(() -> {
158
159            switch (_memoryOperation) {
160                case SetToNull:
161                    memory.setValue(null);
162                    break;
163
164                case SetToString:
165                    memory.setValue(_otherConstantValue);
166                    break;
167
168                case CopyTableCellToMemory:
169                    Object value = _selectTable.evaluateTableData(conditionalNG);
170                    memory.setValue(value);
171                    break;
172
173                case CopyVariableToMemory:
174                    Object variableValue = conditionalNG
175                                    .getSymbolTable().getValue(_otherLocalVariable);
176                    memory.setValue(variableValue);
177                    break;
178
179                case CopyMemoryToMemory:
180                    Memory otherMemory = _selectOtherMemoryNamedBean.evaluateNamedBean(conditionalNG);
181                    if (otherMemory != null) {
182                        memory.setValue(otherMemory.getValue());
183                    } else {
184                        log.warn("setMemory should copy memory to memory but other memory is null");
185                    }
186                    break;
187
188                case CalculateFormula:
189                    if (_otherFormula.isEmpty()) {
190                        memory.setValue(null);
191                    } else {
192                        try {
193                            if (_otherExpressionNode == null) {
194                                return;
195                            }
196                            memory.setValue(_otherExpressionNode.calculate(
197                                    conditionalNG.getSymbolTable()));
198                        } catch (JmriException e) {
199                            ref.set(e);
200                        }
201                    }
202                    break;
203
204                default:
205                    throw new IllegalArgumentException("_memoryOperation has invalid value: {}" + _memoryOperation.name());
206            }
207        });
208
209        if (ref.get() != null) throw ref.get();
210    }
211
212    @Override
213    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
214        throw new UnsupportedOperationException("Not supported.");
215    }
216
217    @Override
218    public int getChildCount() {
219        return 0;
220    }
221
222    @Override
223    public String getShortDescription(Locale locale) {
224        return Bundle.getMessage(locale, "ActionMemory_Short");
225    }
226
227    @Override
228    public String getLongDescription(Locale locale) {
229        String namedBean = _selectNamedBean.getDescription(locale);
230
231        String copyToMemoryName = _selectOtherMemoryNamedBean.getDescription(locale);
232
233        switch (_memoryOperation) {
234            case SetToNull:
235                return Bundle.getMessage(locale, "ActionMemory_Long_Null", namedBean);
236            case SetToString:
237                return Bundle.getMessage(locale, "ActionMemory_Long_Value", namedBean, _otherConstantValue);
238            case CopyVariableToMemory:
239                return Bundle.getMessage(locale, "ActionMemory_Long_CopyVariableToMemory", namedBean, _otherLocalVariable);
240            case CopyMemoryToMemory:
241                return Bundle.getMessage(locale, "ActionMemory_Long_CopyMemoryToMemory", namedBean, copyToMemoryName);
242            case CopyTableCellToMemory:
243                String tableName = _selectTable.getTableNameDescription(locale);
244                String rowName = _selectTable.getTableRowDescription(locale);
245                String columnName = _selectTable.getTableColumnDescription(locale);
246                return Bundle.getMessage(locale, "ActionMemory_Long_CopyTableCellToMemory", namedBean, tableName, rowName, columnName);
247            case CalculateFormula:
248                return Bundle.getMessage(locale, "ActionMemory_Long_Formula", namedBean, _otherFormula);
249            default:
250                throw new IllegalArgumentException("_memoryOperation has invalid value: " + _memoryOperation.name());
251        }
252    }
253
254    /** {@inheritDoc} */
255    @Override
256    public void setup() {
257        // Do nothing
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public void registerListenersForThisClass() {
263        if (!_listenersAreRegistered) {
264            if (_listenToMemory) {
265                _selectOtherMemoryNamedBean.addPropertyChangeListener("value", this);
266            }
267            _selectNamedBean.registerListeners();
268            _listenersAreRegistered = true;
269        }
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public void unregisterListenersForThisClass() {
275        if (_listenersAreRegistered && _listenToMemory) {
276            _selectOtherMemoryNamedBean.removePropertyChangeListener("value", this);
277        }
278        _selectNamedBean.unregisterListeners();
279        _listenersAreRegistered = false;
280    }
281
282    /** {@inheritDoc} */
283    @Override
284    public void propertyChange(PropertyChangeEvent evt) {
285        getConditionalNG().execute();
286    }
287
288    /** {@inheritDoc} */
289    @Override
290    public void disposeMe() {
291    }
292
293
294    public enum MemoryOperation {
295        SetToNull(Bundle.getMessage("ActionMemory_MemoryOperation_SetToNull")),
296        SetToString(Bundle.getMessage("ActionMemory_MemoryOperation_SetToString")),
297        CopyVariableToMemory(Bundle.getMessage("ActionMemory_MemoryOperation_CopyVariableToMemory")),
298        CopyMemoryToMemory(Bundle.getMessage("ActionMemory_MemoryOperation_CopyMemoryToMemory")),
299        CopyTableCellToMemory(Bundle.getMessage("ActionMemory_MemoryOperation_CopyTableCellToMemory")),
300        CalculateFormula(Bundle.getMessage("ActionMemory_MemoryOperation_CalculateFormula"));
301
302        private final String _text;
303
304        private MemoryOperation(String text) {
305            this._text = text;
306        }
307
308        @Override
309        public String toString() {
310            return _text;
311        }
312
313    }
314
315    /** {@inheritDoc} */
316    @Override
317    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
318        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
319        _selectOtherMemoryNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
320    }
321
322    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionMemory.class);
323
324}