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 a string for LogixNG actions and expressions.
021 *
022 * @author Daniel Bergqvist (C) 2022
023 */
024public class LogixNG_SelectString implements VetoableChangeListener {
025
026    private final AbstractBase _base;
027    private final InUse _inUse;
028    private final LogixNG_SelectTable _selectTable;
029    private boolean _onlyDirectAddressingAllowed;
030
031    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
032    private String _value;
033    private String _reference = "";
034    private NamedBeanHandle<Memory> _memoryHandle;
035    private String _localVariable = "";
036    private String _formula = "";
037    private ExpressionNode _expressionNode;
038
039
040    public LogixNG_SelectString(AbstractBase base, InUse inUse) {
041        _base = base;
042        _inUse = inUse;
043        _selectTable = new LogixNG_SelectTable(_base, _inUse);
044    }
045
046    public LogixNG_SelectString(AbstractBase base) {
047        this(base, () -> true);
048    }
049
050    public LogixNG_SelectString(AbstractBase base, String defaultValue) {
051        this(base);
052        _value = defaultValue;
053    }
054
055    public void setOnlyDirectAddressingAllowed() {
056        _onlyDirectAddressingAllowed = true;
057    }
058
059    public boolean isOnlyDirectAddressingAllowed() {
060        return _onlyDirectAddressingAllowed;
061    }
062
063    public void copy(LogixNG_SelectString copy) throws ParserException {
064        copy.setAddressing(_addressing);
065        copy.setValue(_value);
066        copy.setLocalVariable(_localVariable);
067        copy.setReference(_reference);
068        copy.setMemory(_memoryHandle);
069        copy.setFormula(_formula);
070        _selectTable.copy(copy._selectTable);
071    }
072
073    public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException {
074        if (_onlyDirectAddressingAllowed && (addressing != NamedBeanAddressing.Direct)) {
075            throw new IllegalArgumentException("Addressing must be Direct");
076        }
077        this._addressing = addressing;
078        parseFormula();
079    }
080
081    public boolean isDirectAddressing() {
082        return _addressing == NamedBeanAddressing.Direct;
083    }
084
085    public NamedBeanAddressing getAddressing() {
086        return _addressing;
087    }
088
089    public void setValue(@Nonnull String value) {
090        _base.assertListenersAreNotRegistered(log, "setEnum");
091        _value = value;
092    }
093
094    public String getValue() {
095        return _value;
096    }
097
098    public void setReference(@Nonnull String reference) {
099        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
100            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
101        }
102        _reference = reference;
103    }
104
105    public String getReference() {
106        return _reference;
107    }
108
109    public void setMemory(@Nonnull String memoryName) {
110        Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName);
111        if (memory != null) {
112            setMemory(memory);
113        } else {
114            removeMemory();
115            log.warn("memory \"{}\" is not found", memoryName);
116        }
117    }
118
119    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
120        _memoryHandle = handle;
121        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
122        addRemoveVetoListener();
123    }
124
125    public void setMemory(@Nonnull Memory memory) {
126        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
127                .getNamedBeanHandle(memory.getDisplayName(), memory));
128    }
129
130    public void removeMemory() {
131        if (_memoryHandle != null) {
132            _memoryHandle = null;
133            addRemoveVetoListener();
134        }
135    }
136
137    public NamedBeanHandle<Memory> getMemory() {
138        return _memoryHandle;
139    }
140
141    public void setLocalVariable(@Nonnull String localVariable) {
142        _localVariable = localVariable;
143    }
144
145    public String getLocalVariable() {
146        return _localVariable;
147    }
148
149    public void setFormula(@Nonnull String formula) throws ParserException {
150        _formula = formula;
151        parseFormula();
152    }
153
154    public String getFormula() {
155        return _formula;
156    }
157
158    private void parseFormula() throws ParserException {
159        if (_addressing == NamedBeanAddressing.Formula) {
160            Map<String, Variable> variables = new HashMap<>();
161
162            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
163            _expressionNode = parser.parseExpression(_formula);
164        } else {
165            _expressionNode = null;
166        }
167    }
168
169    public LogixNG_SelectTable getSelectTable() {
170        return _selectTable;
171    }
172
173    private void addRemoveVetoListener() {
174        if (_memoryHandle != null) {
175            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
176        } else {
177            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
178        }
179    }
180
181    public String evaluateValue(ConditionalNG conditionalNG) throws JmriException {
182
183        if (_addressing == NamedBeanAddressing.Direct) {
184            return _value;
185        } else {
186            Object val;
187
188            switch (_addressing) {
189                case Reference:
190                    val = ReferenceUtil.getReference(
191                            conditionalNG.getSymbolTable(), _reference);
192                    break;
193
194                case Memory:
195                    val = _memoryHandle.getBean().getValue();
196                    break;
197
198                case LocalVariable:
199                    SymbolTable symbolNamedBean = conditionalNG.getSymbolTable();
200                    val = TypeConversionUtil
201                            .convertToString(symbolNamedBean.getValue(_localVariable), false);
202                    break;
203
204                case Formula:
205                    val = _expressionNode != null
206                            ? _expressionNode.calculate(conditionalNG.getSymbolTable())
207                            : null;
208                    break;
209
210                case Table:
211                    val = _selectTable.evaluateTableData(conditionalNG);
212                    break;
213
214                default:
215                    throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
216            }
217
218            return TypeConversionUtil.convertToString(val, false);
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", _value);
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_SelectString.class);
291}