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 integer for LogixNG actions and expressions.
021 *
022 * @author Daniel Bergqvist (C) 2022
023 */
024public class LogixNG_SelectInteger implements VetoableChangeListener {
025
026    private final AbstractBase _base;
027    private final InUse _inUse;
028    private final LogixNG_SelectTable _selectTable;
029    private final FormatterParserValidator _formatterParserValidator;
030
031    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
032    private int _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_SelectInteger(@Nonnull AbstractBase base) {
041        this(base, new DefaultFormatterParserValidator());
042    }
043
044    public LogixNG_SelectInteger(
045            @Nonnull AbstractBase base,
046            @Nonnull FormatterParserValidator formatterParserValidator) {
047        _base = base;
048        _inUse = () -> true;
049        _selectTable = new LogixNG_SelectTable(_base, _inUse);
050        _formatterParserValidator = formatterParserValidator;
051        _value = _formatterParserValidator.getInitialValue();
052    }
053
054    public void copy(LogixNG_SelectInteger copy) throws ParserException {
055        copy.setAddressing(_addressing);
056        copy.setValue(_value);
057        copy.setLocalVariable(_localVariable);
058        copy.setReference(_reference);
059        copy.setMemory(_memoryHandle);
060        copy.setFormula(_formula);
061        _selectTable.copy(copy._selectTable);
062    }
063
064    @Nonnull
065    public FormatterParserValidator getFormatterParserValidator() {
066        return _formatterParserValidator;
067    }
068
069    public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException {
070        this._addressing = addressing;
071        parseFormula();
072    }
073
074    public boolean isDirectAddressing() {
075        return _addressing == NamedBeanAddressing.Direct;
076    }
077
078    public NamedBeanAddressing getAddressing() {
079        return _addressing;
080    }
081
082    public void setValue(int value) {
083        _base.assertListenersAreNotRegistered(log, "setEnum");
084        _value = value;
085    }
086
087    /**
088     * Get the Integer value.
089     * Value valid only when #isDirectAddressing returns true.
090     * If this returns false use #getDescription(locale) for a String representation.
091     * @return the integer value.
092     */
093    public int getValue() {
094        return _value;
095    }
096
097    public void setReference(@Nonnull String reference) {
098        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
099            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
100        }
101        _reference = reference;
102    }
103
104    public String getReference() {
105        return _reference;
106    }
107
108    public void setMemory(@Nonnull String memoryName) {
109        Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName);
110        if (memory != null) {
111            setMemory(memory);
112        } else {
113            removeMemory();
114            log.warn("memory \"{}\" is not found", memoryName);
115        }
116    }
117
118    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
119        _memoryHandle = handle;
120        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
121        addRemoveVetoListener();
122    }
123
124    public void setMemory(@Nonnull Memory memory) {
125        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
126                .getNamedBeanHandle(memory.getDisplayName(), memory));
127    }
128
129    public void removeMemory() {
130        if (_memoryHandle != null) {
131            _memoryHandle = null;
132            addRemoveVetoListener();
133        }
134    }
135
136    public NamedBeanHandle<Memory> getMemory() {
137        return _memoryHandle;
138    }
139
140    public void setLocalVariable(@Nonnull String localVariable) {
141        _localVariable = localVariable;
142    }
143
144    public String getLocalVariable() {
145        return _localVariable;
146    }
147
148    public void setFormula(@Nonnull String formula) throws ParserException {
149        _formula = formula;
150        parseFormula();
151    }
152
153    public String getFormula() {
154        return _formula;
155    }
156
157    private void parseFormula() throws ParserException {
158        if (_addressing == NamedBeanAddressing.Formula) {
159            Map<String, Variable> variables = new HashMap<>();
160
161            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
162            _expressionNode = parser.parseExpression(_formula);
163        } else {
164            _expressionNode = null;
165        }
166    }
167
168    public LogixNG_SelectTable getSelectTable() {
169        return _selectTable;
170    }
171
172    private void addRemoveVetoListener() {
173        if (_memoryHandle != null) {
174            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
175        } else {
176            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
177        }
178    }
179
180    public int evaluateValue(ConditionalNG conditionalNG) throws JmriException {
181
182        if (_addressing == NamedBeanAddressing.Direct) {
183            return _value;
184        } else {
185            Object val;
186
187            switch (_addressing) {
188                case Reference:
189                    val = ReferenceUtil.getReference(
190                            conditionalNG.getSymbolTable(), _reference);
191                    break;
192
193                case Memory:
194                    val = _memoryHandle.getBean().getValue();
195                    break;
196
197                case LocalVariable:
198                    SymbolTable symbolNamedBean = conditionalNG.getSymbolTable();
199                    val = symbolNamedBean.getValue(_localVariable);
200                    break;
201
202                case Formula:
203                    val = _expressionNode != null
204                            ? _expressionNode.calculate(conditionalNG.getSymbolTable())
205                            : null;
206                    break;
207
208                case Table:
209                    val = _selectTable.evaluateTableData(conditionalNG);
210                    break;
211
212                default:
213                    throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
214            }
215
216            if (val instanceof String) {
217                String validateResult = _formatterParserValidator.validate(val.toString());
218                if (validateResult != null) throw new JmriException(validateResult);
219                return _formatterParserValidator.parse(val.toString());
220            }
221
222            return (int) TypeConversionUtil.convertToLong(val, true, true);
223        }
224    }
225
226    public String getDescription(Locale locale) {
227        return getDescription(locale, true);
228    }
229
230    public String getDescription(Locale locale, boolean thousandsSeparator) {
231        String enumName;
232
233        String memoryName;
234        if (_memoryHandle != null) {
235            memoryName = _memoryHandle.getName();
236        } else {
237            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
238        }
239
240        switch (_addressing) {
241            case Direct:
242                if (thousandsSeparator) {
243                    enumName = Bundle.getMessage(locale, "AddressByDirect", _value);
244                } else {
245                    enumName = Bundle.getMessage(locale, "AddressByDirect", Long.toString(_value));
246                }
247                break;
248
249            case Reference:
250                enumName = Bundle.getMessage(locale, "AddressByReference", _reference);
251                break;
252
253            case Memory:
254                enumName = Bundle.getMessage(locale, "AddressByMemory", memoryName);
255                break;
256
257            case LocalVariable:
258                enumName = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
259                break;
260
261            case Formula:
262                enumName = Bundle.getMessage(locale, "AddressByFormula", _formula);
263                break;
264
265            case Table:
266                enumName = Bundle.getMessage(
267                        locale,
268                        "AddressByTable",
269                        _selectTable.getTableNameDescription(locale),
270                        _selectTable.getTableRowDescription(locale),
271                        _selectTable.getTableColumnDescription(locale));
272                break;
273
274            default:
275                throw new IllegalArgumentException("invalid _addressing: " + _addressing.name());
276        }
277        return enumName;
278    }
279
280    @Override
281    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
282        if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N
283            if (evt.getOldValue() instanceof Memory) {
284                boolean doVeto = false;
285                if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) {
286                    doVeto = true;
287                }
288                if (doVeto) {
289                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
290                    throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N
291                }
292            }
293        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
294            if (evt.getOldValue() instanceof Memory) {
295                if (evt.getOldValue().equals(_memoryHandle.getBean())) {
296                    removeMemory();
297                }
298            }
299        }
300    }
301
302
303    /**
304     * Format, parse and validate.
305     */
306    public interface FormatterParserValidator {
307
308        /**
309         * Get the initial value
310         * @return the initial value
311         */
312        public int getInitialValue();
313
314        /**
315         * Format the value
316         * @param value the value
317         * @return the formatted string
318         */
319        public String format(int value);
320
321        /**
322         * Parse the string
323         * @param str the string
324         * @return the parsed value
325         */
326        public int parse(String str);
327
328        /**
329         * Validates the string
330         * @param str the string
331         * @return null if valid. An error message if not valid
332         */
333        public String validate(String str);
334    }
335
336
337    public static class DefaultFormatterParserValidator
338            implements FormatterParserValidator {
339
340        @Override
341        public int getInitialValue() {
342            return 0;
343        }
344
345        @Override
346        public String format(int value) {
347            return Integer.toString(value);
348        }
349
350        @Override
351        public int parse(String str) {
352            try {
353                return Integer.parseInt(str);
354            } catch (NumberFormatException e) {
355                return getInitialValue();
356            }
357        }
358
359        @Override
360        public String validate(String str) {
361            try {
362                return null;
363            } catch (NumberFormatException e) {
364                return Bundle.getMessage("LogixNG_SelectInteger_MustBeValidInteger");
365            }
366        }
367
368    }
369
370    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectInteger.class);
371}