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