001package jmri.jmrit.logixng.util;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.beans.VetoableChangeListener;
006import java.util.HashMap;
007import java.util.Locale;
008import java.util.Map;
009
010import javax.annotation.Nonnull;
011
012import jmri.*;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.implementation.AbstractBase;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
017import jmri.util.TypeConversionUtil;
018
019/**
020 * Select a boolean for LogixNG actions and expressions.
021 *
022 * @author Daniel Bergqvist (C) 2023
023 */
024public class LogixNG_SelectBoolean implements VetoableChangeListener {
025
026    private final AbstractBase _base;
027    private final InUse _inUse;
028    private final LogixNG_SelectTable _selectTable;
029
030    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
031    private boolean _value;
032    private String _reference = "";
033    private NamedBeanHandle<Memory> _memoryHandle;
034    private String _localVariable = "";
035    private String _formula = "";
036    private ExpressionNode _expressionNode;
037
038
039    public LogixNG_SelectBoolean(@Nonnull AbstractBase base) {
040        _base = base;
041        _inUse = () -> true;
042        _selectTable = new LogixNG_SelectTable(_base, _inUse);
043    }
044
045    public void copy(LogixNG_SelectBoolean copy) throws ParserException {
046        copy.setAddressing(_addressing);
047        copy.setValue(_value);
048        copy.setLocalVariable(_localVariable);
049        copy.setReference(_reference);
050        copy.setMemory(_memoryHandle);
051        copy.setFormula(_formula);
052        _selectTable.copy(copy._selectTable);
053    }
054
055    public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException {
056        this._addressing = addressing;
057        parseFormula();
058    }
059
060    public boolean isDirectAddressing() {
061        return _addressing == NamedBeanAddressing.Direct;
062    }
063
064    public NamedBeanAddressing getAddressing() {
065        return _addressing;
066    }
067
068    public void setValue(boolean value) {
069        _base.assertListenersAreNotRegistered(log, "setEnum");
070        _value = value;
071    }
072
073    public boolean getValue() {
074        return _value;
075    }
076
077    public void setReference(@Nonnull String reference) {
078        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
079            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
080        }
081        _reference = reference;
082    }
083
084    public String getReference() {
085        return _reference;
086    }
087
088    public void setMemory(@Nonnull String memoryName) {
089        Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName);
090        if (memory != null) {
091            setMemory(memory);
092        } else {
093            removeMemory();
094            log.warn("memory \"{}\" is not found", memoryName);
095        }
096    }
097
098    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
099        _memoryHandle = handle;
100        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
101        addRemoveVetoListener();
102    }
103
104    public void setMemory(@Nonnull Memory memory) {
105        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
106                .getNamedBeanHandle(memory.getDisplayName(), memory));
107    }
108
109    public void removeMemory() {
110        if (_memoryHandle != null) {
111            _memoryHandle = null;
112            addRemoveVetoListener();
113        }
114    }
115
116    public NamedBeanHandle<Memory> getMemory() {
117        return _memoryHandle;
118    }
119
120    public void setLocalVariable(@Nonnull String localVariable) {
121        _localVariable = localVariable;
122    }
123
124    public String getLocalVariable() {
125        return _localVariable;
126    }
127
128    public void setFormula(@Nonnull String formula) throws ParserException {
129        _formula = formula;
130        parseFormula();
131    }
132
133    public String getFormula() {
134        return _formula;
135    }
136
137    private void parseFormula() throws ParserException {
138        if (_addressing == NamedBeanAddressing.Formula) {
139            Map<String, Variable> variables = new HashMap<>();
140
141            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
142            _expressionNode = parser.parseExpression(_formula);
143        } else {
144            _expressionNode = null;
145        }
146    }
147
148    public LogixNG_SelectTable getSelectTable() {
149        return _selectTable;
150    }
151
152    private void addRemoveVetoListener() {
153        if (_memoryHandle != null) {
154            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
155        } else {
156            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
157        }
158    }
159
160    public boolean evaluateValue(ConditionalNG conditionalNG) throws JmriException {
161
162        if (_addressing == NamedBeanAddressing.Direct) {
163            return _value;
164        } else {
165            Object val;
166
167            switch (_addressing) {
168                case Reference:
169                    val = ReferenceUtil.getReference(
170                            conditionalNG.getSymbolTable(), _reference);
171                    break;
172
173                case Memory:
174                    val = _memoryHandle.getBean().getValue();
175                    break;
176
177                case LocalVariable:
178                    SymbolTable symbolNamedBean = conditionalNG.getSymbolTable();
179                    val = symbolNamedBean.getValue(_localVariable);
180                    break;
181
182                case Formula:
183                    val = _expressionNode != null
184                            ? _expressionNode.calculate(conditionalNG.getSymbolTable())
185                            : null;
186                    break;
187
188                case Table:
189                    val = _selectTable.evaluateTableData(conditionalNG);
190                    break;
191
192                default:
193                    throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
194            }
195
196            return TypeConversionUtil.convertToBoolean(val, false);
197        }
198    }
199
200    public String getDescription(Locale locale) {
201        String enumName;
202
203        String memoryName;
204        if (_memoryHandle != null) {
205            memoryName = _memoryHandle.getName();
206        } else {
207            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
208        }
209
210        switch (_addressing) {
211            case Direct:
212                enumName = Bundle.getMessage(locale, "AddressByDirect",
213                        _value ? Bundle.getMessage("Boolean_True") : Bundle.getMessage("Boolean_False"));
214                break;
215
216            case Reference:
217                enumName = Bundle.getMessage(locale, "AddressByReference", _reference);
218                break;
219
220            case Memory:
221                enumName = Bundle.getMessage(locale, "AddressByMemory", memoryName);
222                break;
223
224            case LocalVariable:
225                enumName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
226                break;
227
228            case Formula:
229                enumName = Bundle.getMessage(locale, "AddressByFormula", _formula);
230                break;
231
232            case Table:
233                enumName = Bundle.getMessage(
234                        locale,
235                        "AddressByTable",
236                        _selectTable.getTableNameDescription(locale),
237                        _selectTable.getTableRowDescription(locale),
238                        _selectTable.getTableColumnDescription(locale));
239                break;
240
241            default:
242                throw new IllegalArgumentException("invalid _addressing: " + _addressing.name());
243        }
244        return enumName;
245    }
246
247    @Override
248    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
249        if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N
250            if (evt.getOldValue() instanceof Memory) {
251                boolean doVeto = false;
252                if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) {
253                    doVeto = true;
254                }
255                if (doVeto) {
256                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
257                    throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N
258                }
259            }
260        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
261            if (evt.getOldValue() instanceof Memory) {
262                if (evt.getOldValue().equals(_memoryHandle.getBean())) {
263                    removeMemory();
264                }
265            }
266        }
267    }
268
269    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectBoolean.class);
270}