001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.beans.VetoableChangeListener;
006import java.util.*;
007
008import javax.annotation.Nonnull;
009
010import jmri.*;
011import jmri.Logix;
012import jmri.jmrit.entryexit.DestinationPoints;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.ReferenceUtil;
015import jmri.jmrit.logixng.util.parser.*;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
018import jmri.util.ThreadingUtil;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This action triggers a entryExit.
023 * <p>
024 * This action has the Operation enum, similar to EnableLogix and other actions,
025 * despite that's not needed since this action only has one option. But it's
026 * here in case someone wants to add more options later.
027 *
028 * @author Daniel Bergqvist Copyright 2021
029 */
030public class ActionEntryExit extends AbstractDigitalAction implements VetoableChangeListener {
031
032    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
033    private NamedBeanHandle<DestinationPoints> _destinationPointsHandle;
034    private String _reference = "";
035    private String _localVariable = "";
036    private String _formula = "";
037    private ExpressionNode _expressionNode;
038    private NamedBeanAddressing _operationAddressing = NamedBeanAddressing.Direct;
039    private Operation _operationDirect = Operation.SetNXPairEnabled;
040    private String _operationReference = "";
041    private String _operationLocalVariable = "";
042    private String _operationFormula = "";
043    private ExpressionNode _operationExpressionNode;
044
045    public ActionEntryExit(String sys, String user)
046            throws BadUserNameException, BadSystemNameException {
047        super(sys, user);
048    }
049
050    @Override
051    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
052        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
053        String sysName = systemNames.get(getSystemName());
054        String userName = userNames.get(getSystemName());
055        if (sysName == null) sysName = manager.getAutoSystemName();
056        ActionEntryExit copy = new ActionEntryExit(sysName, userName);
057        copy.setComment(getComment());
058        if (_destinationPointsHandle != null) copy.setDestinationPoints(_destinationPointsHandle);
059        copy.setOperationDirect(_operationDirect);
060        copy.setAddressing(_addressing);
061        copy.setFormula(_formula);
062        copy.setLocalVariable(_localVariable);
063        copy.setReference(_reference);
064        copy.setOperationAddressing(_operationAddressing);
065        copy.setOperationFormula(_operationFormula);
066        copy.setOperationLocalVariable(_operationLocalVariable);
067        copy.setOperationReference(_operationReference);
068        return manager.registerAction(copy);
069    }
070
071    public void setDestinationPoints(@Nonnull String entryExitName) {
072        assertListenersAreNotRegistered(log, "setDestinationPoints");
073        DestinationPoints entryExit = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).getNamedBean(entryExitName);
074        if (entryExit != null) {
075            ActionEntryExit.this.setDestinationPoints(entryExit);
076        } else {
077            removeDestinationPoints();
078            log.error("DestinationPoints \"{}\" is not found", entryExitName);
079        }
080    }
081
082    public void setDestinationPoints(@Nonnull NamedBeanHandle<DestinationPoints> handle) {
083        assertListenersAreNotRegistered(log, "setDestinationPoints");
084        _destinationPointsHandle = handle;
085        InstanceManager.getDefault(LogixManager.class).addVetoableChangeListener(this);
086    }
087
088    public void setDestinationPoints(@Nonnull DestinationPoints entryExit) {
089        assertListenersAreNotRegistered(log, "setDestinationPoints");
090        ActionEntryExit.this.setDestinationPoints(InstanceManager.getDefault(NamedBeanHandleManager.class)
091                .getNamedBeanHandle(entryExit.getDisplayName(), entryExit));
092    }
093
094    public void removeDestinationPoints() {
095        assertListenersAreNotRegistered(log, "removeEntryExit");
096        if (_destinationPointsHandle != null) {
097            InstanceManager.getDefault(LogixManager.class).removeVetoableChangeListener(this);
098            _destinationPointsHandle = null;
099        }
100    }
101
102    public NamedBeanHandle<DestinationPoints> getDestinationPoints() {
103        return _destinationPointsHandle;
104    }
105
106    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
107        _addressing = addressing;
108        parseFormula();
109    }
110
111    public NamedBeanAddressing getAddressing() {
112        return _addressing;
113    }
114
115    public void setReference(@Nonnull String reference) {
116        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
117            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
118        }
119        _reference = reference;
120    }
121
122    public String getReference() {
123        return _reference;
124    }
125
126    public void setLocalVariable(@Nonnull String localVariable) {
127        _localVariable = localVariable;
128    }
129
130    public String getLocalVariable() {
131        return _localVariable;
132    }
133
134    public void setFormula(@Nonnull String formula) throws ParserException {
135        _formula = formula;
136        parseFormula();
137    }
138
139    public String getFormula() {
140        return _formula;
141    }
142
143    private void parseFormula() throws ParserException {
144        if (_addressing == NamedBeanAddressing.Formula) {
145            Map<String, Variable> variables = new HashMap<>();
146
147            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
148            _expressionNode = parser.parseExpression(_formula);
149        } else {
150            _expressionNode = null;
151        }
152    }
153
154    public void setOperationAddressing(NamedBeanAddressing addressing) throws ParserException {
155        _operationAddressing = addressing;
156        parseLockFormula();
157    }
158
159    public NamedBeanAddressing getOperationAddressing() {
160        return _operationAddressing;
161    }
162
163    public void setOperationDirect(Operation state) {
164        _operationDirect = state;
165    }
166
167    public Operation getOperationDirect() {
168        return _operationDirect;
169    }
170
171    public void setOperationReference(@Nonnull String reference) {
172        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
173            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
174        }
175        _operationReference = reference;
176    }
177
178    public String getOperationReference() {
179        return _operationReference;
180    }
181
182    public void setOperationLocalVariable(@Nonnull String localVariable) {
183        _operationLocalVariable = localVariable;
184    }
185
186    public String getOperationLocalVariable() {
187        return _operationLocalVariable;
188    }
189
190    public void setOperationFormula(@Nonnull String formula) throws ParserException {
191        _operationFormula = formula;
192        parseLockFormula();
193    }
194
195    public String getLockFormula() {
196        return _operationFormula;
197    }
198
199    private void parseLockFormula() throws ParserException {
200        if (_operationAddressing == NamedBeanAddressing.Formula) {
201            Map<String, Variable> variables = new HashMap<>();
202
203            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
204            _operationExpressionNode = parser.parseExpression(_operationFormula);
205        } else {
206            _operationExpressionNode = null;
207        }
208    }
209
210    @Override
211    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
212        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
213            if (evt.getOldValue() instanceof DestinationPoints) {
214                if (evt.getOldValue().equals(getDestinationPoints().getBean())) {
215                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
216                    throw new PropertyVetoException(Bundle.getMessage("ActionEntryExit_DestinationPointsInUseVeto", getDisplayName()), e); // NOI18N
217                }
218            }
219        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
220            if (evt.getOldValue() instanceof DestinationPoints) {
221                if (evt.getOldValue().equals(getDestinationPoints().getBean())) {
222                    removeDestinationPoints();
223                }
224            }
225        }
226    }
227
228    /** {@inheritDoc} */
229    @Override
230    public Category getCategory() {
231        return Category.ITEM;
232    }
233
234    private String getNewLock() throws JmriException {
235
236        switch (_operationAddressing) {
237            case Reference:
238                return ReferenceUtil.getReference(
239                        getConditionalNG().getSymbolTable(), _operationReference);
240
241            case LocalVariable:
242                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
243                return TypeConversionUtil
244                        .convertToString(symbolTable.getValue(_operationLocalVariable), false);
245
246            case Formula:
247                return _operationExpressionNode != null
248                        ? TypeConversionUtil.convertToString(
249                                _operationExpressionNode.calculate(
250                                        getConditionalNG().getSymbolTable()), false)
251                        : null;
252
253            default:
254                throw new IllegalArgumentException("invalid _addressing state: " + _operationAddressing.name());
255        }
256    }
257
258    /** {@inheritDoc} */
259    @Override
260    public void execute() throws JmriException {
261        DestinationPoints entryExit;
262
263//        System.out.format("ActionEnableLogix.execute: %s%n", getLongDescription());
264
265        switch (_addressing) {
266            case Direct:
267                entryExit = _destinationPointsHandle != null ? _destinationPointsHandle.getBean() : null;
268                break;
269
270            case Reference:
271                String ref = ReferenceUtil.getReference(
272                        getConditionalNG().getSymbolTable(), _reference);
273                entryExit = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class)
274                        .getNamedBean(ref);
275                break;
276
277            case LocalVariable:
278                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
279                entryExit = InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class)
280                        .getNamedBean(TypeConversionUtil
281                                .convertToString(symbolTable.getValue(_localVariable), false));
282                break;
283
284            case Formula:
285                entryExit = _expressionNode != null ?
286                        InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class)
287                                .getNamedBean(TypeConversionUtil
288                                        .convertToString(_expressionNode.calculate(
289                                                getConditionalNG().getSymbolTable()), false))
290                        : null;
291                break;
292
293            default:
294                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
295        }
296
297//        System.out.format("ActionEnableLogix.execute: entryExit: %s%n", entryExit);
298
299        if (entryExit == null) {
300//            log.error("entryExit is null");
301            return;
302        }
303
304        String name = (_operationAddressing != NamedBeanAddressing.Direct)
305                ? getNewLock() : null;
306
307        Operation oper;
308        if ((_operationAddressing == NamedBeanAddressing.Direct)) {
309            oper = _operationDirect;
310        } else {
311            oper = Operation.valueOf(name);
312        }
313
314        // Variables used in lambda must be effectively final
315        Operation theOper = oper;
316
317        ThreadingUtil.runOnLayoutWithJmriException(() -> {
318            switch (theOper) {
319                case SetNXPairEnabled:
320                    entryExit.setEnabled(true);
321                    break;
322                case SetNXPairDisabled:
323                    entryExit.setEnabled(false);
324                    break;
325                case SetNXPairSegment:
326                    jmri.InstanceManager.getDefault(jmri.jmrit.entryexit.EntryExitPairs.class).
327                            setSingleSegmentRoute(entryExit.getSystemName());
328                    break;
329                default:
330                    throw new IllegalArgumentException("invalid oper state: " + theOper.name());
331            }
332        });
333    }
334
335    @Override
336    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
337        throw new UnsupportedOperationException("Not supported.");
338    }
339
340    @Override
341    public int getChildCount() {
342        return 0;
343    }
344
345    @Override
346    public String getShortDescription(Locale locale) {
347        return Bundle.getMessage(locale, "ActionEntryExit_Short");
348    }
349
350    @Override
351    public String getLongDescription(Locale locale) {
352        String namedBean;
353        String state;
354
355        switch (_addressing) {
356            case Direct:
357                String entryExitName;
358                if (_destinationPointsHandle != null) {
359                    entryExitName = _destinationPointsHandle.getBean().getDisplayName();
360                } else {
361                    entryExitName = Bundle.getMessage(locale, "BeanNotSelected");
362                }
363                namedBean = Bundle.getMessage(locale, "AddressByDirect", entryExitName);
364                break;
365
366            case Reference:
367                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
368                break;
369
370            case LocalVariable:
371                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
372                break;
373
374            case Formula:
375                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
376                break;
377
378            default:
379                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
380        }
381
382        switch (_operationAddressing) {
383            case Direct:
384                state = Bundle.getMessage(locale, "AddressByDirect", _operationDirect._text);
385                break;
386
387            case Reference:
388                state = Bundle.getMessage(locale, "AddressByReference", _operationReference);
389                break;
390
391            case LocalVariable:
392                state = Bundle.getMessage(locale, "AddressByLocalVariable", _operationLocalVariable);
393                break;
394
395            case Formula:
396                state = Bundle.getMessage(locale, "AddressByFormula", _operationFormula);
397                break;
398
399            default:
400                throw new IllegalArgumentException("invalid _stateAddressing state: " + _operationAddressing.name());
401        }
402
403        return Bundle.getMessage(locale, "ActionEntryExit_Long", namedBean, state);
404    }
405
406    /** {@inheritDoc} */
407    @Override
408    public void setup() {
409        // Do nothing
410    }
411
412    /** {@inheritDoc} */
413    @Override
414    public void registerListenersForThisClass() {
415    }
416
417    /** {@inheritDoc} */
418    @Override
419    public void unregisterListenersForThisClass() {
420    }
421
422    /** {@inheritDoc} */
423    @Override
424    public void disposeMe() {
425    }
426
427
428    public enum Operation {
429        SetNXPairEnabled(Bundle.getMessage("ActionEntryExit_SetNXPairEnabled")),
430        SetNXPairDisabled(Bundle.getMessage("ActionEntryExit_SetNXPairDisabled")),
431        SetNXPairSegment(Bundle.getMessage("ActionEntryExit_SetNXPairSegment"));
432
433        private final String _text;
434
435        private Operation(String text) {
436            this._text = text;
437        }
438
439        @Override
440        public String toString() {
441            return _text;
442        }
443
444    }
445
446    /** {@inheritDoc} */
447    @Override
448    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
449        log.debug("getUsageReport :: ActionEntryExit: bean = {}, report = {}", cdl, report);
450        if (getDestinationPoints() != null && bean.equals(getDestinationPoints().getBean())) {
451            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
452        }
453    }
454
455    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionEntryExit.class);
456
457}