001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.beans.VetoableChangeListener;
006import java.util.*;
007import java.util.concurrent.atomic.AtomicReference;
008
009import javax.annotation.Nonnull;
010
011import jmri.*;
012import jmri.jmrit.logixng.*;
013import jmri.jmrit.logixng.util.ReferenceUtil;
014import jmri.jmrit.logixng.util.parser.*;
015import jmri.jmrit.logixng.util.parser.ExpressionNode;
016import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
017import jmri.util.TypeConversionUtil;
018
019/**
020 * Evaluates the state of a SignalHead.
021 *
022 * @author Daniel Bergqvist Copyright 2020
023 */
024public class ActionSignalHead extends AbstractDigitalAction
025        implements VetoableChangeListener {
026
027    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
028    private NamedBeanHandle<SignalHead> _signalHeadHandle;
029    private String _reference = "";
030    private String _localVariable = "";
031    private String _formula = "";
032    private ExpressionNode _expressionNode;
033
034    private NamedBeanAddressing _operationAddressing = NamedBeanAddressing.Direct;
035    private OperationType _operationType = OperationType.Appearance;
036    private String _operationReference = "";
037    private String _operationLocalVariable = "";
038    private String _operationFormula = "";
039    private ExpressionNode _operationExpressionNode;
040
041    private NamedBeanAddressing _appearanceAddressing = NamedBeanAddressing.Direct;
042    private int _signalHeadAppearance = SignalHead.DARK;
043    private String _appearanceReference = "";
044    private String _appearanceLocalVariable = "";
045    private String _appearanceFormula = "";
046    private ExpressionNode _appearanceExpressionNode;
047
048    private NamedBeanHandle<SignalHead> _exampleSignalHeadHandle;
049
050
051    public ActionSignalHead(String sys, String user)
052            throws BadUserNameException, BadSystemNameException {
053        super(sys, user);
054    }
055
056    @Override
057    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
058        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
059        String sysName = systemNames.get(getSystemName());
060        String userName = userNames.get(getSystemName());
061        if (sysName == null) sysName = manager.getAutoSystemName();
062        ActionSignalHead copy = new ActionSignalHead(sysName, userName);
063        copy.setComment(getComment());
064        if (_signalHeadHandle != null) copy.setSignalHead(_signalHeadHandle);
065        copy.setAppearance(_signalHeadAppearance);
066        copy.setAddressing(_addressing);
067        copy.setFormula(_formula);
068        copy.setLocalVariable(_localVariable);
069        copy.setReference(_reference);
070        copy.setOperationAddressing(_operationAddressing);
071        copy.setOperationType(_operationType);
072        copy.setOperationFormula(_operationFormula);
073        copy.setOperationLocalVariable(_operationLocalVariable);
074        copy.setOperationReference(_operationReference);
075        copy.setAppearanceAddressing(_appearanceAddressing);
076        copy.setAppearanceFormula(_appearanceFormula);
077        copy.setAppearanceLocalVariable(_appearanceLocalVariable);
078        copy.setAppearanceReference(_appearanceReference);
079        copy.setExampleSignalHead(_exampleSignalHeadHandle);
080        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
081    }
082
083    public void setSignalHead(@Nonnull String signalHeadName) {
084        assertListenersAreNotRegistered(log, "setSignalHead");
085        SignalHead signalHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(signalHeadName);
086        if (signalHead != null) {
087            setSignalHead(signalHead);
088        } else {
089            removeSignalHead();
090            log.warn("signalHead \"{}\" is not found", signalHeadName);
091        }
092    }
093
094    public void setSignalHead(@Nonnull NamedBeanHandle<SignalHead> handle) {
095        assertListenersAreNotRegistered(log, "setSignalHead");
096        _signalHeadHandle = handle;
097        InstanceManager.getDefault(SignalHeadManager.class).addVetoableChangeListener(this);
098    }
099
100    public void setSignalHead(@Nonnull SignalHead signalHead) {
101        assertListenersAreNotRegistered(log, "setSignalHead");
102        setSignalHead(InstanceManager.getDefault(NamedBeanHandleManager.class)
103                .getNamedBeanHandle(signalHead.getDisplayName(), signalHead));
104    }
105
106    public void removeSignalHead() {
107        assertListenersAreNotRegistered(log, "setSignalHead");
108        if (_signalHeadHandle != null) {
109            InstanceManager.getDefault(SignalHeadManager.class).removeVetoableChangeListener(this);
110            _signalHeadHandle = null;
111        }
112    }
113
114    public NamedBeanHandle<SignalHead> getSignalHead() {
115        return _signalHeadHandle;
116    }
117
118    public void setExampleSignalHead(@Nonnull String signalHeadName) {
119        assertListenersAreNotRegistered(log, "setExampleSignalHead");
120        SignalHead signalHead = InstanceManager.getDefault(SignalHeadManager.class).getSignalHead(signalHeadName);
121        if (signalHead != null) {
122            setExampleSignalHead(signalHead);
123        } else {
124            removeExampleSignalHead();
125            log.warn("signalHead \"{}\" is not found", signalHeadName);
126        }
127    }
128
129    public void setExampleSignalHead(@Nonnull NamedBeanHandle<SignalHead> handle) {
130        assertListenersAreNotRegistered(log, "setExampleSignalHead");
131        _exampleSignalHeadHandle = handle;
132        InstanceManager.getDefault(SignalHeadManager.class).addVetoableChangeListener(this);
133    }
134
135    public void setExampleSignalHead(@Nonnull SignalHead signalHead) {
136        assertListenersAreNotRegistered(log, "setExampleSignalHead");
137        setExampleSignalHead(InstanceManager.getDefault(NamedBeanHandleManager.class)
138                .getNamedBeanHandle(signalHead.getDisplayName(), signalHead));
139    }
140
141    public void removeExampleSignalHead() {
142        assertListenersAreNotRegistered(log, "removeExampleSignalHead");
143        if (_exampleSignalHeadHandle != null) {
144            InstanceManager.getDefault(SignalHeadManager.class).removeVetoableChangeListener(this);
145            _exampleSignalHeadHandle = null;
146        }
147    }
148
149    public NamedBeanHandle<SignalHead> getExampleSignalHead() {
150        return _exampleSignalHeadHandle;
151    }
152
153    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
154        _addressing = addressing;
155        parseFormula();
156    }
157
158    public NamedBeanAddressing getAddressing() {
159        return _addressing;
160    }
161
162    public void setReference(@Nonnull String reference) {
163        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
164            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
165        }
166        _reference = reference;
167    }
168
169    public String getReference() {
170        return _reference;
171    }
172
173    public void setLocalVariable(@Nonnull String localVariable) {
174        _localVariable = localVariable;
175    }
176
177    public String getLocalVariable() {
178        return _localVariable;
179    }
180
181    public void setFormula(@Nonnull String formula) throws ParserException {
182        _formula = formula;
183        parseFormula();
184    }
185
186    public String getFormula() {
187        return _formula;
188    }
189
190    private void parseFormula() throws ParserException {
191        if (_addressing == NamedBeanAddressing.Formula) {
192            Map<String, Variable> variables = new HashMap<>();
193
194            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
195            _expressionNode = parser.parseExpression(_formula);
196        } else {
197            _expressionNode = null;
198        }
199    }
200
201    public void setOperationAddressing(NamedBeanAddressing addressing) throws ParserException {
202        _operationAddressing = addressing;
203        parseOperationFormula();
204    }
205
206    public NamedBeanAddressing getOperationAddressing() {
207        return _operationAddressing;
208    }
209
210    public void setOperationType(OperationType operationType) {
211        _operationType = operationType;
212    }
213
214    public OperationType getOperationType() {
215        return _operationType;
216    }
217
218    public void setOperationReference(@Nonnull String reference) {
219        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
220            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
221        }
222        _operationReference = reference;
223    }
224
225    public String getOperationReference() {
226        return _operationReference;
227    }
228
229    public void setOperationLocalVariable(@Nonnull String localVariable) {
230        _operationLocalVariable = localVariable;
231    }
232
233    public String getOperationLocalVariable() {
234        return _operationLocalVariable;
235    }
236
237    public void setOperationFormula(@Nonnull String formula) throws ParserException {
238        _operationFormula = formula;
239        parseOperationFormula();
240    }
241
242    public String getOperationFormula() {
243        return _operationFormula;
244    }
245
246    private void parseOperationFormula() throws ParserException {
247        if (_operationAddressing == NamedBeanAddressing.Formula) {
248            Map<String, Variable> variables = new HashMap<>();
249
250            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
251            _operationExpressionNode = parser.parseExpression(_operationFormula);
252        } else {
253            _operationExpressionNode = null;
254        }
255    }
256
257    public void setAppearanceAddressing(NamedBeanAddressing addressing) throws ParserException {
258        _appearanceAddressing = addressing;
259        parseAppearanceFormula();
260    }
261
262    public NamedBeanAddressing getAppearanceAddressing() {
263        return _appearanceAddressing;
264    }
265
266    public void setAppearance(int appearance) {
267        _signalHeadAppearance = appearance;
268    }
269
270    public int getAppearance() {
271        return _signalHeadAppearance;
272    }
273
274    public void setAppearanceReference(@Nonnull String reference) {
275        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
276            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
277        }
278        _appearanceReference = reference;
279    }
280
281    public String getAppearanceReference() {
282        return _appearanceReference;
283    }
284
285    public void setAppearanceLocalVariable(@Nonnull String localVariable) {
286        _appearanceLocalVariable = localVariable;
287    }
288
289    public String getAppearanceLocalVariable() {
290        return _appearanceLocalVariable;
291    }
292
293    public void setAppearanceFormula(@Nonnull String formula) throws ParserException {
294        _appearanceFormula = formula;
295        parseAppearanceFormula();
296    }
297
298    public String getAppearanceFormula() {
299        return _appearanceFormula;
300    }
301
302    private void parseAppearanceFormula() throws ParserException {
303        if (_appearanceAddressing == NamedBeanAddressing.Formula) {
304            Map<String, Variable> variables = new HashMap<>();
305
306            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
307            _appearanceExpressionNode = parser.parseExpression(_appearanceFormula);
308        } else {
309            _appearanceExpressionNode = null;
310        }
311    }
312
313    @Override
314    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
315        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
316            if (evt.getOldValue() instanceof SignalHead) {
317                if ((_signalHeadHandle != null)
318                        && (evt.getOldValue().equals(_signalHeadHandle.getBean()))) {
319                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
320                    throw new PropertyVetoException(Bundle.getMessage("SignalHead_SignalHeadInUseSignalHeadActionVeto", getDisplayName()), e); // NOI18N
321                }
322                if ((_exampleSignalHeadHandle != null)
323                        && (evt.getOldValue().equals(_exampleSignalHeadHandle.getBean()))) {
324                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
325                    throw new PropertyVetoException(Bundle.getMessage("SignalHead_SignalHeadInUseSignalHeadActionVeto", getDisplayName()), e); // NOI18N
326                }
327            }
328        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
329            if (evt.getOldValue() instanceof SignalHead) {
330                if ((_signalHeadHandle != null)
331                        && (evt.getOldValue().equals(_signalHeadHandle.getBean()))) {
332                    removeSignalHead();
333                }
334                if ((_exampleSignalHeadHandle != null)
335                        && (evt.getOldValue().equals(_exampleSignalHeadHandle.getBean()))) {
336                    removeExampleSignalHead();
337                }
338            }
339        }
340    }
341
342    /** {@inheritDoc} */
343    @Override
344    public Category getCategory() {
345        return Category.ITEM;
346    }
347
348    private int getAppearanceFromName(String name) {
349        if (_signalHeadHandle == null) throw new UnsupportedOperationException("_signalHeadHandle is null");
350
351        SignalHead sh = _signalHeadHandle.getBean();
352        String[] keys = sh.getValidStateKeys();
353        for (int i=0; i < keys.length; i++) {
354            if (name.equals(keys[i])) return sh.getValidStates()[i];
355        }
356
357        throw new IllegalArgumentException("Appearance "+name+" is not valid for signal head "+sh.getSystemName());
358    }
359
360    private int getNewAppearance() throws JmriException {
361
362        switch (_appearanceAddressing) {
363            case Direct:
364                return _signalHeadAppearance;
365
366            case Reference:
367                return getAppearanceFromName(ReferenceUtil.getReference(
368                        getConditionalNG().getSymbolTable(), _appearanceReference));
369
370            case LocalVariable:
371                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
372                return getAppearanceFromName(TypeConversionUtil
373                        .convertToString(symbolTable.getValue(_appearanceLocalVariable), false));
374
375            case Formula:
376                return _appearanceExpressionNode != null
377                        ? getAppearanceFromName(TypeConversionUtil.convertToString(
378                                _appearanceExpressionNode.calculate(
379                                        getConditionalNG().getSymbolTable()), false))
380                        : -1;
381
382            default:
383                throw new IllegalArgumentException("invalid _aspectAddressing state: " + _appearanceAddressing.name());
384        }
385    }
386
387    private OperationType getOperation() throws JmriException {
388
389        String oper = "";
390        try {
391            switch (_operationAddressing) {
392                case Direct:
393                    return _operationType;
394
395                case Reference:
396                    oper = ReferenceUtil.getReference(
397                            getConditionalNG().getSymbolTable(), _operationReference);
398                    return OperationType.valueOf(oper);
399
400                case LocalVariable:
401                    SymbolTable symbolTable = getConditionalNG().getSymbolTable();
402                    oper = TypeConversionUtil
403                            .convertToString(symbolTable.getValue(_operationLocalVariable), false);
404                    return OperationType.valueOf(oper);
405
406                case Formula:
407                    if (_appearanceExpressionNode != null) {
408                        oper = TypeConversionUtil.convertToString(
409                                _operationExpressionNode.calculate(
410                                        getConditionalNG().getSymbolTable()), false);
411                        return OperationType.valueOf(oper);
412                    } else {
413                        return null;
414                    }
415                default:
416                    throw new IllegalArgumentException("invalid _addressing state: " + _operationAddressing.name());
417            }
418        } catch (IllegalArgumentException e) {
419            throw new JmriException("Unknown operation: "+oper, e);
420        }
421    }
422
423    /** {@inheritDoc} */
424    @Override
425    public void execute() throws JmriException {
426        SignalHead signalHead;
427
428//        System.out.format("ActionSignalHead.execute: %s%n", getLongDescription());
429
430        switch (_addressing) {
431            case Direct:
432                signalHead = _signalHeadHandle != null ? _signalHeadHandle.getBean() : null;
433                break;
434
435            case Reference:
436                String ref = ReferenceUtil.getReference(
437                        getConditionalNG().getSymbolTable(), _reference);
438                signalHead = InstanceManager.getDefault(SignalHeadManager.class)
439                        .getNamedBean(ref);
440                break;
441
442            case LocalVariable:
443                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
444                signalHead = InstanceManager.getDefault(SignalHeadManager.class)
445                        .getNamedBean(TypeConversionUtil
446                                .convertToString(symbolTable.getValue(_localVariable), false));
447                break;
448
449            case Formula:
450                signalHead = _expressionNode != null ?
451                        InstanceManager.getDefault(SignalHeadManager.class)
452                                .getNamedBean(TypeConversionUtil
453                                        .convertToString(_expressionNode.calculate(
454                                                getConditionalNG().getSymbolTable()), false))
455                        : null;
456                break;
457
458            default:
459                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
460        }
461
462//        System.out.format("ActionSignalHead.execute: sensor: %s%n", sensor);
463
464        if (signalHead == null) {
465//            log.warn("signalHead is null");
466            return;
467        }
468
469        OperationType operation = getOperation();
470
471        AtomicReference<JmriException> ref = new AtomicReference<>();
472        jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> {
473            try {
474                switch (operation) {
475                    case Appearance:
476                        int newAppearance = getNewAppearance();
477                        if (newAppearance != -1) {
478                            signalHead.setAppearance(newAppearance);
479                        }
480                        break;
481                    case Lit:
482                        signalHead.setLit(true);
483                        break;
484                    case NotLit:
485                        signalHead.setLit(false);
486                        break;
487                    case Held:
488                        signalHead.setHeld(true);
489                        break;
490                    case NotHeld:
491                        signalHead.setHeld(false);
492                        break;
493        //            case PermissiveSmlDisabled:
494        //                signalHead.setPermissiveSmlDisabled(true);
495        //                break;
496        //            case PermissiveSmlNotDisabled:
497        //                signalHead.setPermissiveSmlDisabled(false);
498        //                break;
499                    default:
500                        throw new JmriException("Unknown enum: "+_operationType.name());
501                }
502            } catch (JmriException e) {
503                ref.set(e);
504            }
505        });
506        if (ref.get() != null) throw ref.get();
507    }
508
509    @Override
510    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
511        throw new UnsupportedOperationException("Not supported.");
512    }
513
514    @Override
515    public int getChildCount() {
516        return 0;
517    }
518
519    @Override
520    public String getShortDescription(Locale locale) {
521        return Bundle.getMessage(locale, "SignalHead_Short");
522    }
523
524    @Override
525    public String getLongDescription(Locale locale) {
526        String namedBean;
527        String operation;
528        String appearance;
529
530        switch (_addressing) {
531            case Direct:
532                String sensorName;
533                if (_signalHeadHandle != null) {
534                    sensorName = _signalHeadHandle.getBean().getDisplayName();
535                } else {
536                    sensorName = Bundle.getMessage(locale, "BeanNotSelected");
537                }
538                namedBean = Bundle.getMessage(locale, "AddressByDirect", sensorName);
539                break;
540
541            case Reference:
542                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
543                break;
544
545            case LocalVariable:
546                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
547                break;
548
549            case Formula:
550                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
551                break;
552
553            default:
554                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
555        }
556
557        switch (_operationAddressing) {
558            case Direct:
559                operation = Bundle.getMessage(locale, "AddressByDirect", _operationType._text);
560                break;
561
562            case Reference:
563                operation = Bundle.getMessage(locale, "AddressByReference", _operationReference);
564                break;
565
566            case LocalVariable:
567                operation = Bundle.getMessage(locale, "AddressByLocalVariable", _operationLocalVariable);
568                break;
569
570            case Formula:
571                operation = Bundle.getMessage(locale, "AddressByFormula", _operationFormula);
572                break;
573
574            default:
575                throw new IllegalArgumentException("invalid _operationAddressing state: " + _operationAddressing.name());
576        }
577
578        switch (_appearanceAddressing) {
579            case Direct:
580                String a = "";
581                if ((_signalHeadHandle != null) && (_signalHeadHandle.getBean() != null)) {
582                    a = _signalHeadHandle.getBean().getAppearanceName(_signalHeadAppearance);
583                }
584                appearance = Bundle.getMessage(locale, "AddressByDirect", a);
585                break;
586
587            case Reference:
588                appearance = Bundle.getMessage(locale, "AddressByReference", _appearanceReference);
589                break;
590
591            case LocalVariable:
592                appearance = Bundle.getMessage(locale, "AddressByLocalVariable", _appearanceLocalVariable);
593                break;
594
595            case Formula:
596                appearance = Bundle.getMessage(locale, "AddressByFormula", _appearanceFormula);
597                break;
598
599            default:
600                throw new IllegalArgumentException("invalid _stateAddressing state: " + _appearanceAddressing.name());
601        }
602
603        if (_operationAddressing == NamedBeanAddressing.Direct) {
604            if (_operationType == OperationType.Appearance) {
605                return Bundle.getMessage(locale, "SignalHead_LongAppearance", namedBean, appearance);
606            } else {
607                return Bundle.getMessage(locale, "SignalHead_Long", namedBean, operation);
608            }
609        } else {
610            return Bundle.getMessage(locale, "SignalHead_LongUnknownOper", namedBean, operation, appearance);
611        }
612    }
613
614    /** {@inheritDoc} */
615    @Override
616    public void setup() {
617        // Do nothing
618    }
619
620    /** {@inheritDoc} */
621    @Override
622    public void registerListenersForThisClass() {
623    }
624
625    /** {@inheritDoc} */
626    @Override
627    public void unregisterListenersForThisClass() {
628    }
629
630    /** {@inheritDoc} */
631    @Override
632    public void disposeMe() {
633    }
634
635
636
637    public enum OperationType {
638        Appearance(Bundle.getMessage("SignalHeadOperationType_Appearance")),
639        Lit(Bundle.getMessage("SignalHeadOperationType_Lit")),
640        NotLit(Bundle.getMessage("SignalHeadOperationType_NotLit")),
641        Held(Bundle.getMessage("SignalHeadOperationType_Held")),
642        NotHeld(Bundle.getMessage("SignalHeadOperationType_NotHeld"));
643
644        private final String _text;
645
646        private OperationType(String text) {
647            this._text = text;
648        }
649
650        @Override
651        public String toString() {
652            return _text;
653        }
654
655    }
656
657    /** {@inheritDoc} */
658    @Override
659    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
660        log.debug("getUsageReport :: ActionSignalHead: bean = {}, report = {}", cdl, report);
661        if (getSignalHead() != null && bean.equals(getSignalHead().getBean())) {
662            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
663        }
664        if (getExampleSignalHead() != null && bean.equals(getExampleSignalHead().getBean())) {
665            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
666        }
667    }
668
669    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionSignalHead.class);
670
671}