001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005import java.util.concurrent.atomic.AtomicReference;
006
007import javax.annotation.CheckForNull;
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.jmrit.logixng.*;
012import jmri.jmrit.logixng.util.ReferenceUtil;
013import jmri.jmrit.logixng.util.parser.*;
014import jmri.jmrit.logixng.util.parser.ExpressionNode;
015import jmri.util.ThreadingUtil;
016import jmri.util.TypeConversionUtil;
017
018/**
019 * This action sets the value of a memory.
020 *
021 * @author Daniel Bergqvist Copyright 2018
022 */
023public class ActionMemory extends AbstractDigitalAction
024        implements PropertyChangeListener, VetoableChangeListener {
025
026    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
027    private NamedBeanHandle<Memory> _memoryHandle;
028    private String _reference = "";
029    private String _localVariable = "";
030    private String _formula = "";
031    private ExpressionNode _expressionNode;
032    private NamedBeanHandle<Memory> _otherMemoryHandle;
033    private MemoryOperation _memoryOperation = MemoryOperation.SetToString;
034    private String _otherConstantValue = "";
035    private String _otherLocalVariable = "";
036    private String _otherFormula = "";
037    private ExpressionNode _otherExpressionNode;
038    private boolean _listenToMemory = true;
039//    private boolean _listenToMemory = false;
040
041    public ActionMemory(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 ParserException {
048        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
049        String sysName = systemNames.get(getSystemName());
050        String userName = userNames.get(getSystemName());
051        if (sysName == null) sysName = manager.getAutoSystemName();
052        ActionMemory copy = new ActionMemory(sysName, userName);
053        copy.setComment(getComment());
054        if (_memoryHandle != null) copy.setMemory(_memoryHandle);
055        copy.setAddressing(_addressing);
056        copy.setFormula(_formula);
057        copy.setLocalVariable(_localVariable);
058        copy.setReference(_reference);
059        if (_otherMemoryHandle != null) copy.setOtherMemory(_otherMemoryHandle);
060        copy.setMemoryOperation(_memoryOperation);
061        copy.setOtherConstantValue(_otherConstantValue);
062        copy.setOtherLocalVariable(_otherLocalVariable);
063        copy.setOtherFormula(_otherFormula);
064        copy.setListenToMemory(_listenToMemory);
065        return manager.registerAction(copy);
066    }
067
068    public void setMemory(@Nonnull String memoryName) {
069        assertListenersAreNotRegistered(log, "setMemory");
070        Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName);
071        if (memory != null) {
072            setMemory(memory);
073        } else {
074            removeMemory();
075            log.warn("memory \"{}\" is not found", memoryName);
076        }
077    }
078
079    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
080        assertListenersAreNotRegistered(log, "setMemory");
081        _memoryHandle = handle;
082        addRemoveVetoListener();
083    }
084
085    public void setMemory(@Nonnull Memory memory) {
086        assertListenersAreNotRegistered(log, "setMemory");
087        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
088                .getNamedBeanHandle(memory.getDisplayName(), memory));
089    }
090
091    public void removeMemory() {
092        assertListenersAreNotRegistered(log, "removeMemory");
093        if (_memoryHandle != null) {
094            _memoryHandle = null;
095            addRemoveVetoListener();
096        }
097    }
098
099    public NamedBeanHandle<Memory> getMemory() {
100        return _memoryHandle;
101    }
102
103    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
104        _addressing = addressing;
105        parseOtherFormula();
106    }
107
108    public NamedBeanAddressing getAddressing() {
109        return _addressing;
110    }
111
112    public void setReference(@Nonnull String reference) {
113        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
114            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
115        }
116        _reference = reference;
117    }
118
119    public String getReference() {
120        return _reference;
121    }
122
123    public void setLocalVariable(@Nonnull String localVariable) {
124        _localVariable = localVariable;
125    }
126
127    public String getLocalVariable() {
128        return _localVariable;
129    }
130
131    public void setFormula(@Nonnull String formula) throws ParserException {
132        _formula = formula;
133        parseFormula();
134    }
135
136    public String getFormula() {
137        return _formula;
138    }
139
140    private void parseFormula() throws ParserException {
141        if (_addressing == NamedBeanAddressing.Formula) {
142            Map<String, Variable> variables = new HashMap<>();
143
144            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
145            _expressionNode = parser.parseExpression(_formula);
146        } else {
147            _expressionNode = null;
148        }
149    }
150
151    public void setOtherMemory(@Nonnull String memoryName) {
152        assertListenersAreNotRegistered(log, "setOtherMemory");
153        MemoryManager memoryManager = InstanceManager.getDefault(MemoryManager.class);
154        Memory memory = memoryManager.getMemory(memoryName);
155        if (memory != null) {
156            setOtherMemory(memory);
157        } else {
158            removeOtherMemory();
159            log.warn("memory \"{}\" is not found", memoryName);
160        }
161    }
162
163    public void setOtherMemory(@Nonnull NamedBeanHandle<Memory> handle) {
164        assertListenersAreNotRegistered(log, "setOtherMemory");
165        _otherMemoryHandle = handle;
166        addRemoveVetoListener();
167    }
168
169    public void setOtherMemory(@Nonnull Memory memory) {
170        assertListenersAreNotRegistered(log, "setOtherMemory");
171        setOtherMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
172                .getNamedBeanHandle(memory.getDisplayName(), memory));
173    }
174
175    public void removeOtherMemory() {
176        assertListenersAreNotRegistered(log, "removeOtherMemory");
177        if (_otherMemoryHandle != null) {
178            _otherMemoryHandle = null;
179            addRemoveVetoListener();
180        }
181    }
182
183    public NamedBeanHandle<Memory> getOtherMemory() {
184        return _otherMemoryHandle;
185    }
186
187    public void setOtherLocalVariable(@Nonnull String localVariable) {
188        assertListenersAreNotRegistered(log, "setOtherLocalVariable");
189        _otherLocalVariable = localVariable;
190    }
191
192    public String getOtherLocalVariable() {
193        return _otherLocalVariable;
194    }
195
196    public void setOtherConstantValue(String constantValue) {
197        _otherConstantValue = constantValue;
198    }
199
200    public String getConstantValue() {
201        return _otherConstantValue;
202    }
203
204    public void setOtherFormula(String formula) throws ParserException {
205        _otherFormula = formula;
206        parseOtherFormula();
207    }
208
209    public String getOtherFormula() {
210        return _otherFormula;
211    }
212
213    public void setListenToMemory(boolean listenToMemory) {
214        this._listenToMemory = listenToMemory;
215    }
216
217    public boolean getListenToMemory() {
218        return _listenToMemory;
219    }
220
221    public void setMemoryOperation(MemoryOperation state) throws ParserException {
222        _memoryOperation = state;
223        parseOtherFormula();
224    }
225
226    public MemoryOperation getMemoryOperation() {
227        return _memoryOperation;
228    }
229
230    private void parseOtherFormula() throws ParserException {
231        if (_memoryOperation == MemoryOperation.CalculateFormula) {
232            Map<String, Variable> variables = new HashMap<>();
233/*
234            SymbolTable symbolTable =
235                    InstanceManager.getDefault(LogixNG_Manager.class)
236                            .getSymbolTable();
237
238            if (symbolTable == null && 1==1) return;    // Why does this happens?
239//            if (symbolTable == null && 1==1) return;    // Nothing we can do if we don't have a symbol table
240            if (symbolTable == null) throw new RuntimeException("Daniel AA");
241            if (symbolTable.getSymbols() == null) throw new RuntimeException("Daniel BB");
242            if (symbolTable.getSymbols().values() == null) throw new RuntimeException("Daniel BB");
243
244            for (SymbolTable.Symbol symbol : symbolTable.getSymbols().values()) {
245                variables.put(symbol.getName(),
246                        new LocalVariableExpressionVariable(symbol.getName()));
247            }
248*/
249            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
250            _otherExpressionNode = parser.parseExpression(_otherFormula);
251        } else {
252            _otherExpressionNode = null;
253        }
254    }
255
256    private void addRemoveVetoListener() {
257        if ((_memoryHandle != null) || (_otherMemoryHandle != null)) {
258            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
259        } else {
260            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
261        }
262    }
263
264    @Override
265    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
266        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
267            if (evt.getOldValue() instanceof Memory) {
268                boolean doVeto = false;
269                if ((_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) doVeto = true;
270                if ((_otherMemoryHandle != null) && evt.getOldValue().equals(_otherMemoryHandle.getBean())) doVeto = true;
271                if (doVeto) {
272                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
273                    throw new PropertyVetoException(Bundle.getMessage("ActionMemory_MemoryInUseMemoryActionVeto", getDisplayName()), e); // NOI18N
274                }
275            }
276        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
277            if (evt.getOldValue() instanceof Memory) {
278                if ((_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) {
279                    removeMemory();
280                }
281                if ((_otherMemoryHandle != null) && evt.getOldValue().equals(_otherMemoryHandle.getBean())) {
282                    removeOtherMemory();
283                }
284            }
285        }
286    }
287
288    /** {@inheritDoc} */
289    @Override
290    public Category getCategory() {
291        return Category.ITEM;
292    }
293
294    /** {@inheritDoc} */
295    @Override
296    public void execute() throws JmriException {
297
298        Memory memory;
299
300//        System.out.format("ActionLight.execute: %s%n", getLongDescription());
301
302        switch (_addressing) {
303            case Direct:
304                memory = _memoryHandle != null ? _memoryHandle.getBean() : null;
305                break;
306
307            case Reference:
308                String ref = ReferenceUtil.getReference(
309                        getConditionalNG().getSymbolTable(), _reference);
310                memory = InstanceManager.getDefault(MemoryManager.class)
311                        .getNamedBean(ref);
312                break;
313
314            case LocalVariable:
315                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
316                memory = InstanceManager.getDefault(MemoryManager.class)
317                        .getNamedBean(TypeConversionUtil
318                                .convertToString(symbolTable.getValue(_localVariable), false));
319                break;
320
321            case Formula:
322                memory = _expressionNode != null ?
323                        InstanceManager.getDefault(MemoryManager.class)
324                                .getNamedBean(TypeConversionUtil
325                                        .convertToString(_expressionNode.calculate(
326                                                getConditionalNG().getSymbolTable()), false))
327                        : null;
328                break;
329
330            default:
331                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
332        }
333
334//        System.out.format("ActionMemory.execute: Memory: %s%n", memory);
335
336        if (memory == null) {
337//            log.warn("memory is null");
338            return;
339        }
340
341        AtomicReference<JmriException> ref = new AtomicReference<>();
342
343        ThreadingUtil.runOnLayoutWithJmriException(() -> {
344
345            switch (_memoryOperation) {
346                case SetToNull:
347                    memory.setValue(null);
348                    break;
349
350                case SetToString:
351                    memory.setValue(_otherConstantValue);
352                    break;
353
354                case CopyVariableToMemory:
355                    Object variableValue = getConditionalNG()
356                                    .getSymbolTable().getValue(_otherLocalVariable);
357                    memory.setValue(variableValue);
358                    break;
359
360                case CopyMemoryToMemory:
361                    if (_otherMemoryHandle != null) {
362                        memory.setValue(_otherMemoryHandle.getBean().getValue());
363                    } else {
364                        log.warn("setMemory should copy memory to memory but other memory is null");
365                    }
366                    break;
367
368                case CalculateFormula:
369                    if (_otherFormula.isEmpty()) {
370                        memory.setValue(null);
371                    } else {
372                        try {
373                            if (_otherExpressionNode == null) {
374                                return;
375                            }
376                            memory.setValue(_otherExpressionNode.calculate(
377                                    getConditionalNG().getSymbolTable()));
378                        } catch (JmriException e) {
379                            ref.set(e);
380                        }
381                    }
382                    break;
383
384                default:
385                    throw new IllegalArgumentException("_memoryOperation has invalid value: {}" + _memoryOperation.name());
386            }
387        });
388
389        if (ref.get() != null) throw ref.get();
390    }
391
392    @Override
393    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
394        throw new UnsupportedOperationException("Not supported.");
395    }
396
397    @Override
398    public int getChildCount() {
399        return 0;
400    }
401
402    @Override
403    public String getShortDescription(Locale locale) {
404        return Bundle.getMessage(locale, "ActionMemory_Short");
405    }
406
407    @Override
408    public String getLongDescription(Locale locale) {
409        String memoryName;
410        if (_memoryHandle != null) {
411            memoryName = _memoryHandle.getBean().getDisplayName();
412        } else {
413            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
414        }
415
416        String copyToMemoryName;
417        if (_otherMemoryHandle != null) {
418            copyToMemoryName = _otherMemoryHandle.getBean().getDisplayName();
419        } else {
420            copyToMemoryName = Bundle.getMessage(locale, "BeanNotSelected");
421        }
422
423        switch (_memoryOperation) {
424            case SetToNull:
425                return Bundle.getMessage(locale, "ActionMemory_Long_Null", memoryName);
426            case SetToString:
427                return Bundle.getMessage(locale, "ActionMemory_Long_Value", memoryName, _otherConstantValue);
428            case CopyVariableToMemory:
429                return Bundle.getMessage(locale, "ActionMemory_Long_CopyVariableToMemory", memoryName, _otherLocalVariable);
430            case CopyMemoryToMemory:
431                return Bundle.getMessage(locale, "ActionMemory_Long_CopyMemoryToMemory", memoryName, copyToMemoryName);
432            case CalculateFormula:
433                return Bundle.getMessage(locale, "ActionMemory_Long_Formula", memoryName, _otherFormula);
434            default:
435                throw new IllegalArgumentException("_memoryOperation has invalid value: " + _memoryOperation.name());
436        }
437    }
438
439    /** {@inheritDoc} */
440    @Override
441    public void setup() {
442        // Do nothing
443    }
444
445    /** {@inheritDoc} */
446    @Override
447    public void registerListenersForThisClass() {
448        if (!_listenersAreRegistered && (_otherMemoryHandle != null)) {
449            if (_listenToMemory) {
450                _otherMemoryHandle.getBean().addPropertyChangeListener("value", this);
451            }
452            _listenersAreRegistered = true;
453        }
454    }
455
456    /** {@inheritDoc} */
457    @Override
458    public void unregisterListenersForThisClass() {
459        if (_listenersAreRegistered) {
460            if (_listenToMemory && (_otherMemoryHandle != null)) {
461                _otherMemoryHandle.getBean().addPropertyChangeListener("value", this);
462            }
463            _listenersAreRegistered = false;
464        }
465    }
466
467    /** {@inheritDoc} */
468    @Override
469    public void propertyChange(PropertyChangeEvent evt) {
470        getConditionalNG().execute();
471    }
472
473    /** {@inheritDoc} */
474    @Override
475    public void disposeMe() {
476    }
477
478
479    public enum MemoryOperation {
480        SetToNull(Bundle.getMessage("ActionMemory_MemoryOperation_SetToNull")),
481        SetToString(Bundle.getMessage("ActionMemory_MemoryOperation_SetToString")),
482        CopyVariableToMemory(Bundle.getMessage("ActionMemory_MemoryOperation_CopyVariableToMemory")),
483        CopyMemoryToMemory(Bundle.getMessage("ActionMemory_MemoryOperation_CopyMemoryToMemory")),
484        CalculateFormula(Bundle.getMessage("ActionMemory_MemoryOperation_CalculateFormula"));
485
486        private final String _text;
487
488        private MemoryOperation(String text) {
489            this._text = text;
490        }
491
492        @Override
493        public String toString() {
494            return _text;
495        }
496
497    }
498
499    /** {@inheritDoc} */
500    @Override
501    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
502        log.debug("getUsageReport :: ActionMemory: bean = {}, report = {}", cdl, report);
503        if (getMemory() != null && bean.equals(getMemory().getBean())) {
504            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
505        }
506        if (getOtherMemory() != null && bean.equals(getOtherMemory().getBean())) {
507            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
508        }
509    }
510
511    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionMemory.class);
512
513}