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