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