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