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