001package jmri.jmrit.logixng.util;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.CheckForNull;
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.logixng.*;
011import jmri.jmrit.logixng.implementation.AbstractBase;
012import jmri.jmrit.logixng.util.parser.*;
013import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
014import jmri.util.TypeConversionUtil;
015
016/**
017 * Select namedBean for LogixNG actions and expressions.
018 *
019 * @param <E> the type of the named bean
020 *
021 * @author Daniel Bergqvist (C) 2022
022 */
023public class LogixNG_SelectNamedBean<E extends NamedBean> implements VetoableChangeListener {
024
025    private final AbstractBase _base;
026    private final InUse _inUse;
027    private final Class<E> _class;
028    private final Manager<E> _manager;
029    private final LogixNG_SelectTable _selectTable;
030    private boolean _onlyDirectAddressingAllowed;
031
032    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
033    private NamedBeanHandle<E> _handle;
034    private String _reference = "";
035    private NamedBeanHandle<Memory> _memoryHandle;
036    private String _localVariable = "";
037    private String _formula = "";
038    private ExpressionNode _expressionNode;
039
040    private String _delayedNamedBean;
041
042
043    public LogixNG_SelectNamedBean(AbstractBase base, Class<E> clazz, Manager<E> manager) {
044        _base = base;
045        _inUse = () -> true;
046        _class = clazz;
047        _manager = manager;
048        _selectTable = new LogixNG_SelectTable(_base, _inUse);
049    }
050
051    public LogixNG_SelectNamedBean(AbstractBase base, Class<E> clazz, Manager<E> manager, InUse inUse) {
052        _base = base;
053        _inUse = inUse;
054        _class = clazz;
055        _manager = manager;
056        _selectTable = new LogixNG_SelectTable(_base, _inUse);
057    }
058
059    public void setOnlyDirectAddressingAllowed() {
060        _onlyDirectAddressingAllowed = true;
061    }
062
063    public boolean getOnlyDirectAddressingAllowed() {
064        return _onlyDirectAddressingAllowed;
065    }
066
067    public void copy(LogixNG_SelectNamedBean<E> copy) throws ParserException {
068        copy.setAddressing(_addressing);
069        if (_handle != null) copy.setNamedBean(_handle);
070        copy.setLocalVariable(_localVariable);
071        copy.setReference(_reference);
072        copy.setMemory(_memoryHandle);
073        copy.setFormula(_formula);
074        _selectTable.copy(copy._selectTable);
075    }
076
077    public Manager<E> getManager() {
078        return _manager;
079    }
080
081    public void setAddressing(@Nonnull NamedBeanAddressing addressing) throws ParserException {
082        if (_onlyDirectAddressingAllowed && (addressing != NamedBeanAddressing.Direct)) {
083            throw new IllegalArgumentException("Addressing must be Direct");
084        }
085        this._addressing = addressing;
086        parseFormula();
087    }
088
089    public boolean isDirectAddressing() {
090        return _addressing == NamedBeanAddressing.Direct;
091    }
092
093    public NamedBeanAddressing getAddressing() {
094        return _addressing;
095    }
096
097    public void setDelayedNamedBean(@Nonnull String name) {
098        _delayedNamedBean = name;
099    }
100
101    public void setup() {
102        if (_delayedNamedBean != null) setNamedBean(_delayedNamedBean);
103    }
104
105    public void setNamedBean(@Nonnull String name) {
106        _base.assertListenersAreNotRegistered(log, "setNamedBean");
107        E namedBean = _manager.getNamedBean(name);
108        if (namedBean != null) {
109            setNamedBean(name, namedBean);
110        } else {
111            removeNamedBean();
112            log.error("{} \"{}\" is not found", _manager.getBeanTypeHandled(), name);
113        }
114    }
115
116    public void setNamedBean(@Nonnull NamedBeanHandle<E> handle) {
117        _base.assertListenersAreNotRegistered(log, "setNamedBean");
118        _handle = handle;
119        _manager.addVetoableChangeListener(this);
120    }
121
122    public void setNamedBean(@Nonnull E namedBean) {
123        setNamedBean(namedBean.getDisplayName(), namedBean);
124    }
125
126    public void setNamedBean(@Nonnull String name, @Nonnull E namedBean) {
127        _base.assertListenersAreNotRegistered(log, "setNamedBean");
128        setNamedBean(InstanceManager.getDefault(NamedBeanHandleManager.class)
129                .getNamedBeanHandle(name, namedBean));
130    }
131
132    public void removeNamedBean() {
133        _base.assertListenersAreNotRegistered(log, "setNamedBean");
134        if (_handle != null) {
135            _manager.removeVetoableChangeListener(this);
136            _handle = null;
137        }
138    }
139
140    public E getBean() {
141        if (_handle != null) {
142            return _handle.getBean();
143        } else {
144            return null;
145        }
146    }
147
148    public NamedBeanHandle<E> getNamedBean() {
149        return _handle;
150    }
151
152    public E getNamedBeanIfDirectAddressing() {
153        if ((_handle != null) && (this._addressing == NamedBeanAddressing.Direct)) {
154            return _handle.getBean();
155        }
156        return null;
157    }
158
159    public void setReference(@Nonnull String reference) {
160        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
161            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
162        }
163        _reference = reference;
164    }
165
166    public String getReference() {
167        return _reference;
168    }
169
170    public void setMemory(@Nonnull String memoryName) {
171        Memory memory = InstanceManager.getDefault(MemoryManager.class).getMemory(memoryName);
172        if (memory != null) {
173            setMemory(memory);
174        } else {
175            removeMemory();
176            log.warn("memory \"{}\" is not found", memoryName);
177        }
178    }
179
180    public void setMemory(@Nonnull NamedBeanHandle<Memory> handle) {
181        _memoryHandle = handle;
182        InstanceManager.memoryManagerInstance().addVetoableChangeListener(this);
183        addRemoveVetoListener();
184    }
185
186    public void setMemory(@Nonnull Memory memory) {
187        setMemory(InstanceManager.getDefault(NamedBeanHandleManager.class)
188                .getNamedBeanHandle(memory.getDisplayName(), memory));
189    }
190
191    public void removeMemory() {
192        if (_memoryHandle != null) {
193            _memoryHandle = null;
194            addRemoveVetoListener();
195        }
196    }
197
198    public NamedBeanHandle<Memory> getMemory() {
199        return _memoryHandle;
200    }
201
202    public void setLocalVariable(@Nonnull String localVariable) {
203        _localVariable = localVariable;
204    }
205
206    public String getLocalVariable() {
207        return _localVariable;
208    }
209
210    public void setFormula(@Nonnull String formula) throws ParserException {
211        _formula = formula;
212        parseFormula();
213    }
214
215    public String getFormula() {
216        return _formula;
217    }
218
219    private void parseFormula() throws ParserException {
220        if (_addressing == NamedBeanAddressing.Formula) {
221            Map<String, Variable> variables = new HashMap<>();
222
223            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
224            _expressionNode = parser.parseExpression(_formula);
225        } else {
226            _expressionNode = null;
227        }
228    }
229
230    public LogixNG_SelectTable getSelectTable() {
231        return _selectTable;
232    }
233
234    private void addRemoveVetoListener() {
235        if (_memoryHandle != null) {
236            InstanceManager.getDefault(MemoryManager.class).addVetoableChangeListener(this);
237        } else {
238            InstanceManager.getDefault(MemoryManager.class).removeVetoableChangeListener(this);
239        }
240    }
241
242    public E evaluateNamedBean(ConditionalNG conditionalNG) throws JmriException {
243
244        if (_addressing == NamedBeanAddressing.Direct) {
245            return _handle != null ? _handle.getBean() : null;
246        } else {
247            String name;
248
249            switch (_addressing) {
250                case Reference:
251                    name = ReferenceUtil.getReference(
252                            conditionalNG.getSymbolTable(), _reference);
253                    break;
254
255                case LocalVariable:
256                    SymbolTable symbolNamedBean = conditionalNG.getSymbolTable();
257                    name = TypeConversionUtil
258                            .convertToString(symbolNamedBean.getValue(_localVariable), false);
259                    break;
260
261                case Memory:
262                    name = TypeConversionUtil
263                            .convertToString(_memoryHandle.getBean().getValue(), false);
264                    break;
265
266                case Formula:
267                    name = _expressionNode  != null
268                            ? TypeConversionUtil.convertToString(
269                                    _expressionNode.calculate(
270                                            conditionalNG.getSymbolTable()), false)
271                            : null;
272                    break;
273
274                case Table:
275                    name = TypeConversionUtil.convertToString(
276                            _selectTable.evaluateTableData(conditionalNG), false);
277                    break;
278
279                default:
280                    throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
281            }
282
283            E namedBean = null;
284            if (name != null) {
285                namedBean = _manager.getNamedBean(name);
286            }
287            return namedBean;
288        }
289    }
290
291    public String getDescription(Locale locale) {
292        String namedBean;
293
294        String memoryName;
295        if (_memoryHandle != null) {
296            memoryName = _memoryHandle.getName();
297        } else {
298            memoryName = Bundle.getMessage(locale, "BeanNotSelected");
299        }
300
301        switch (_addressing) {
302            case Direct:
303                String namedBeanName;
304                if (_handle != null) {
305                    namedBeanName = _handle.getBean().getDisplayName();
306                } else {
307                    namedBeanName = Bundle.getMessage(locale, "BeanNotSelected");
308                }
309                namedBean = Bundle.getMessage(locale, "AddressByDirect", namedBeanName);
310                break;
311
312            case Reference:
313                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
314                break;
315
316            case Memory:
317                namedBean = Bundle.getMessage(locale, "AddressByMemory", memoryName);
318                break;
319
320            case LocalVariable:
321                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
322                break;
323
324            case Formula:
325                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
326                break;
327
328            case Table:
329                namedBean = Bundle.getMessage(
330                        locale,
331                        "AddressByTable",
332                        _selectTable.getTableNameDescription(locale),
333                        _selectTable.getTableRowDescription(locale),
334                        _selectTable.getTableColumnDescription(locale));
335                break;
336
337            default:
338                throw new IllegalArgumentException("invalid _addressing: " + _addressing.name());
339        }
340        return namedBean;
341    }
342
343    @Override
344    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
345        if ("CanDelete".equals(evt.getPropertyName()) && _inUse.isInUse()) { // No I18N
346            if (_inUse.isInUse() && (_class.isAssignableFrom(evt.getOldValue().getClass()))) {
347                if (evt.getOldValue().equals(getNamedBean().getBean())) {
348                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
349                    throw new PropertyVetoException(Bundle.getMessage("InUseVeto", _base.getDisplayName(), _base.getShortDescription()), e);
350                }
351            }
352            if (evt.getOldValue() instanceof Memory) {
353                boolean doVeto = false;
354                if ((_addressing == NamedBeanAddressing.Memory) && (_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) {
355                    doVeto = true;
356                }
357                if (doVeto) {
358                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
359                    throw new PropertyVetoException(Bundle.getMessage("MemoryInUseMemoryExpressionVeto", _base.getDisplayName()), e); // NOI18N
360                }
361            }
362        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
363            if (_class.isAssignableFrom(evt.getOldValue().getClass())) {
364                if (evt.getOldValue().equals(getNamedBean().getBean())) {
365                    removeNamedBean();
366                }
367            }
368            if (evt.getOldValue() instanceof Memory) {
369                if ((_memoryHandle != null) && evt.getOldValue().equals(_memoryHandle.getBean())) {
370                    removeMemory();
371                }
372            }
373        }
374    }
375
376    /**
377     * Add a {@link java.beans.PropertyChangeListener} for a specific property.
378     *
379     * @param listener The PropertyChangeListener to be added
380     */
381    public void addPropertyChangeListener(
382            @CheckForNull PropertyChangeListener listener) {
383        if ((_addressing == NamedBeanAddressing.Direct) && (_handle != null)) {
384            _handle.getBean().addPropertyChangeListener(listener);
385        }
386    }
387
388    /**
389     * Add a {@link java.beans.PropertyChangeListener} for a specific property.
390     *
391     * @param propertyName The name of the property to listen on.
392     * @param listener     The PropertyChangeListener to be added
393     */
394    public void addPropertyChangeListener(
395            @CheckForNull String propertyName,
396            @CheckForNull PropertyChangeListener listener) {
397        if ((_addressing == NamedBeanAddressing.Direct) && (_handle != null)) {
398            _handle.getBean().addPropertyChangeListener(propertyName, listener);
399        }
400    }
401
402    /**
403     * Remove the specified listener of the specified property from this object.
404     *
405     * @param listener The {@link java.beans.PropertyChangeListener} to
406     *                 remove.
407     */
408    public void removePropertyChangeListener(
409            @CheckForNull PropertyChangeListener listener) {
410        if (_handle != null) {
411            _handle.getBean().removePropertyChangeListener(listener);
412        }
413    }
414
415    /**
416     * Remove the specified listener of the specified property from this object.
417     *
418     * @param propertyName The name of the property to stop listening to.
419     * @param listener     The {@link java.beans.PropertyChangeListener} to
420     *                     remove.
421     */
422    public void removePropertyChangeListener(
423            @CheckForNull String propertyName,
424            @CheckForNull PropertyChangeListener listener) {
425        if (_handle != null) {
426            _handle.getBean().removePropertyChangeListener(propertyName, listener);
427        }
428    }
429
430    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl, Base base, Type type) {
431        log.debug("getUsageReport :: {}: bean = {}, report = {}", base.getShortDescription(), cdl, report);
432        if (_handle != null && bean.equals(_handle.getBean())) {
433            report.add(new NamedBeanUsageReport(type.toString(), cdl, base.getLongDescription()));
434        }
435    }
436
437    public static enum Type {
438        Action("LogixNGAction"),
439        Expression("LogixNGExpression");
440
441        private final String _descr;
442
443        private Type(String descr) {
444            this._descr = descr;
445        }
446
447        @Override
448        public String toString() {
449            return _descr;
450        }
451    }
452
453    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNG_SelectNamedBean.class);
454}