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