001package jmri.jmrit.logixng.actions;
002
003import java.beans.*;
004import java.util.*;
005import java.util.concurrent.atomic.AtomicReference;
006
007import javax.annotation.Nonnull;
008
009import jmri.*;
010import jmri.jmrit.logixng.*;
011import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
012import jmri.jmrit.logixng.util.ReferenceUtil;
013import jmri.jmrit.logixng.util.parser.*;
014import jmri.util.TypeConversionUtil;
015
016/**
017 * Evaluates the state of a SignalMast.
018 *
019 * @author Daniel Bergqvist Copyright 2020
020 */
021public class ActionSignalMast extends AbstractDigitalAction
022        implements PropertyChangeListener, VetoableChangeListener {
023
024    private final LogixNG_SelectNamedBean<SignalMast> _selectNamedBean =
025            new LogixNG_SelectNamedBean<>(
026                    this, SignalMast.class, InstanceManager.getDefault(SignalMastManager.class), this);
027
028    private NamedBeanAddressing _operationAddressing = NamedBeanAddressing.Direct;
029    private OperationType _operationType = OperationType.Aspect;
030    private String _operationReference = "";
031    private String _operationLocalVariable = "";
032    private String _operationFormula = "";
033    private ExpressionNode _operationExpressionNode;
034
035    private NamedBeanAddressing _aspectAddressing = NamedBeanAddressing.Direct;
036    private String _signalMastAspect = "";
037    private String _aspectReference = "";
038    private String _aspectLocalVariable = "";
039    private String _aspectFormula = "";
040    private ExpressionNode _aspectExpressionNode;
041
042    private final LogixNG_SelectNamedBean<SignalMast> _selectExampleNamedBean =
043            new LogixNG_SelectNamedBean<>(
044                    this, SignalMast.class, InstanceManager.getDefault(SignalMastManager.class), this);
045
046
047    public ActionSignalMast(String sys, String user)
048            throws BadUserNameException, BadSystemNameException {
049        super(sys, user);
050    }
051
052    @Override
053    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException {
054        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
055        String sysName = systemNames.get(getSystemName());
056        String userName = userNames.get(getSystemName());
057        if (sysName == null) sysName = manager.getAutoSystemName();
058        ActionSignalMast copy = new ActionSignalMast(sysName, userName);
059        copy.setComment(getComment());
060        _selectNamedBean.copy(copy._selectNamedBean);
061        copy.setAspect(_signalMastAspect);
062        copy.setOperationAddressing(_operationAddressing);
063        copy.setOperationType(_operationType);
064        copy.setOperationFormula(_operationFormula);
065        copy.setOperationLocalVariable(_operationLocalVariable);
066        copy.setOperationReference(_operationReference);
067        copy.setAspectAddressing(_aspectAddressing);
068        copy.setAspectFormula(_aspectFormula);
069        copy.setAspectLocalVariable(_aspectLocalVariable);
070        copy.setAspectReference(_aspectReference);
071        _selectExampleNamedBean.copy(copy._selectExampleNamedBean);
072        return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames);
073    }
074
075    public LogixNG_SelectNamedBean<SignalMast> getSelectNamedBean() {
076        return _selectNamedBean;
077    }
078
079    public LogixNG_SelectNamedBean<SignalMast> getSelectExampleNamedBean() {
080        return _selectExampleNamedBean;
081    }
082
083    public void setOperationAddressing(NamedBeanAddressing addressing) throws ParserException {
084        _operationAddressing = addressing;
085        parseOperationFormula();
086    }
087
088    public NamedBeanAddressing getOperationAddressing() {
089        return _operationAddressing;
090    }
091
092    public void setOperationType(OperationType operationType) {
093        _operationType = operationType;
094    }
095
096    public OperationType getOperationType() {
097        return _operationType;
098    }
099
100    public void setOperationReference(@Nonnull String reference) {
101        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
102            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
103        }
104        _operationReference = reference;
105    }
106
107    public String getOperationReference() {
108        return _operationReference;
109    }
110
111    public void setOperationLocalVariable(@Nonnull String localVariable) {
112        _operationLocalVariable = localVariable;
113    }
114
115    public String getOperationLocalVariable() {
116        return _operationLocalVariable;
117    }
118
119    public void setOperationFormula(@Nonnull String formula) throws ParserException {
120        _operationFormula = formula;
121        parseOperationFormula();
122    }
123
124    public String getOperationFormula() {
125        return _operationFormula;
126    }
127
128    private void parseOperationFormula() throws ParserException {
129        if (_operationAddressing == NamedBeanAddressing.Formula) {
130            Map<String, Variable> variables = new HashMap<>();
131
132            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
133            _operationExpressionNode = parser.parseExpression(_operationFormula);
134        } else {
135            _operationExpressionNode = null;
136        }
137    }
138
139    public void setAspectAddressing(NamedBeanAddressing addressing) throws ParserException {
140        _aspectAddressing = addressing;
141        parseAspectFormula();
142    }
143
144    public NamedBeanAddressing getAspectAddressing() {
145        return _aspectAddressing;
146    }
147
148    public void setAspect(String aspect) {
149        if (aspect == null) _signalMastAspect = "";
150        else _signalMastAspect = aspect;
151    }
152
153    public String getAspect() {
154        return _signalMastAspect;
155    }
156
157    public void setAspectReference(@Nonnull String reference) {
158        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
159            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
160        }
161        _aspectReference = reference;
162    }
163
164    public String getAspectReference() {
165        return _aspectReference;
166    }
167
168    public void setAspectLocalVariable(@Nonnull String localVariable) {
169        _aspectLocalVariable = localVariable;
170    }
171
172    public String getAspectLocalVariable() {
173        return _aspectLocalVariable;
174    }
175
176    public void setAspectFormula(@Nonnull String formula) throws ParserException {
177        _aspectFormula = formula;
178        parseAspectFormula();
179    }
180
181    public String getAspectFormula() {
182        return _aspectFormula;
183    }
184
185    private void parseAspectFormula() throws ParserException {
186        if (_aspectAddressing == NamedBeanAddressing.Formula) {
187            Map<String, Variable> variables = new HashMap<>();
188
189            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
190            _aspectExpressionNode = parser.parseExpression(_aspectFormula);
191        } else {
192            _aspectExpressionNode = null;
193        }
194    }
195
196    /** {@inheritDoc} */
197    @Override
198    public Category getCategory() {
199        return Category.ITEM;
200    }
201
202    private String getNewAspect(ConditionalNG conditionalNG) throws JmriException {
203
204        switch (_aspectAddressing) {
205            case Direct:
206                return _signalMastAspect;
207
208            case Reference:
209                return ReferenceUtil.getReference(
210                        conditionalNG.getSymbolTable(), _aspectReference);
211
212            case LocalVariable:
213                SymbolTable symbolTable = conditionalNG.getSymbolTable();
214                return TypeConversionUtil
215                        .convertToString(symbolTable.getValue(_aspectLocalVariable), false);
216
217            case Formula:
218                return _aspectExpressionNode != null
219                        ? TypeConversionUtil.convertToString(
220                                _aspectExpressionNode.calculate(
221                                        conditionalNG.getSymbolTable()), false)
222                        : "";
223
224            default:
225                throw new IllegalArgumentException("invalid _aspectAddressing state: " + _aspectAddressing.name());
226        }
227    }
228
229    private OperationType getOperation(ConditionalNG conditionalNG) throws JmriException {
230
231        String oper = "";
232        try {
233            switch (_operationAddressing) {
234                case Direct:
235                    return _operationType;
236
237                case Reference:
238                    oper = ReferenceUtil.getReference(
239                            conditionalNG.getSymbolTable(), _operationReference);
240                    return OperationType.valueOf(oper);
241
242                case LocalVariable:
243                    SymbolTable symbolTable = conditionalNG.getSymbolTable();
244                    oper = TypeConversionUtil
245                            .convertToString(symbolTable.getValue(_operationLocalVariable), false);
246                    return OperationType.valueOf(oper);
247
248                case Formula:
249                    if (_aspectExpressionNode != null) {
250                        oper = TypeConversionUtil.convertToString(
251                                _operationExpressionNode.calculate(
252                                        conditionalNG.getSymbolTable()), false);
253                        return OperationType.valueOf(oper);
254                    } else {
255                        return null;
256                    }
257                default:
258                    throw new IllegalArgumentException("invalid _addressing state: " + _operationAddressing.name());
259            }
260        } catch (IllegalArgumentException e) {
261            throw new JmriException("Unknown operation: "+oper, e);
262        }
263    }
264
265    /** {@inheritDoc} */
266    @Override
267    public void execute() throws JmriException {
268        final ConditionalNG conditionalNG = getConditionalNG();
269
270        SignalMast signalMast = _selectNamedBean.evaluateNamedBean(conditionalNG);
271
272        if (signalMast == null) return;
273
274        OperationType operation = getOperation(conditionalNG);
275
276        AtomicReference<JmriException> ref = new AtomicReference<>();
277        jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> {
278            try {
279                switch (operation) {
280                    case Aspect:
281                        String newAspect = getNewAspect(conditionalNG);
282                        if (!newAspect.isEmpty()) {
283                            signalMast.setAspect(newAspect);
284                        }
285                        break;
286                    case Lit:
287                        signalMast.setLit(true);
288                        break;
289                    case NotLit:
290                        signalMast.setLit(false);
291                        break;
292                    case Held:
293                        signalMast.setHeld(true);
294                        break;
295                    case NotHeld:
296                        signalMast.setHeld(false);
297                        break;
298                    case PermissiveSmlDisabled:
299                        signalMast.setPermissiveSmlDisabled(true);
300                        break;
301                    case PermissiveSmlNotDisabled:
302                        signalMast.setPermissiveSmlDisabled(false);
303                        break;
304                    default:
305                        throw new JmriException("Unknown enum: "+_operationType.name());
306                }
307            } catch (JmriException e) {
308                ref.set(e);
309            }
310        });
311        if (ref.get() != null) throw ref.get();
312    }
313
314    @Override
315    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
316        throw new UnsupportedOperationException("Not supported.");
317    }
318
319    @Override
320    public int getChildCount() {
321        return 0;
322    }
323
324    @Override
325    public String getShortDescription(Locale locale) {
326        return Bundle.getMessage(locale, "SignalMast_Short");
327    }
328
329    @Override
330    public String getLongDescription(Locale locale) {
331        String namedBean = _selectNamedBean.getDescription(locale);
332        String operation;
333        String aspect;
334
335        switch (_operationAddressing) {
336            case Direct:
337                operation = Bundle.getMessage(locale, "AddressByDirect", _operationType._text);
338                break;
339
340            case Reference:
341                operation = Bundle.getMessage(locale, "AddressByReference", _operationReference);
342                break;
343
344            case LocalVariable:
345                operation = Bundle.getMessage(locale, "AddressByLocalVariable", _operationLocalVariable);
346                break;
347
348            case Formula:
349                operation = Bundle.getMessage(locale, "AddressByFormula", _operationFormula);
350                break;
351
352            default:
353                throw new IllegalArgumentException("invalid _operationAddressing state: " + _operationAddressing.name());
354        }
355
356        switch (_aspectAddressing) {
357            case Direct:
358                aspect = Bundle.getMessage(locale, "AddressByDirect", _signalMastAspect);
359                break;
360
361            case Reference:
362                aspect = Bundle.getMessage(locale, "AddressByReference", _aspectReference);
363                break;
364
365            case LocalVariable:
366                aspect = Bundle.getMessage(locale, "AddressByLocalVariable", _aspectLocalVariable);
367                break;
368
369            case Formula:
370                aspect = Bundle.getMessage(locale, "AddressByFormula", _aspectFormula);
371                break;
372
373            default:
374                throw new IllegalArgumentException("invalid _stateAddressing state: " + _aspectAddressing.name());
375        }
376
377        if (_operationAddressing == NamedBeanAddressing.Direct) {
378            if (_operationType == OperationType.Aspect) {
379                return Bundle.getMessage(locale, "SignalMast_LongAspect", namedBean, aspect);
380            } else {
381                return Bundle.getMessage(locale, "SignalMast_Long", namedBean, operation);
382            }
383        } else {
384            return Bundle.getMessage(locale, "SignalMast_LongUnknownOper", namedBean, operation, aspect);
385        }
386    }
387
388    /** {@inheritDoc} */
389    @Override
390    public void setup() {
391        // Do nothing
392    }
393
394    /** {@inheritDoc} */
395    @Override
396    public void registerListenersForThisClass() {
397        _selectNamedBean.registerListeners();
398    }
399
400    /** {@inheritDoc} */
401    @Override
402    public void unregisterListenersForThisClass() {
403        _selectNamedBean.unregisterListeners();
404    }
405
406    /** {@inheritDoc} */
407    @Override
408    public void propertyChange(PropertyChangeEvent evt) {
409        getConditionalNG().execute();
410    }
411
412    /** {@inheritDoc} */
413    @Override
414    public void disposeMe() {
415    }
416
417
418
419    public enum OperationType {
420        Aspect(Bundle.getMessage("SignalMastOperationType_Aspect")),
421        Lit(Bundle.getMessage("SignalMastOperationType_Lit")),
422        NotLit(Bundle.getMessage("SignalMastOperationType_NotLit")),
423        Held(Bundle.getMessage("SignalMastOperationType_Held")),
424        NotHeld(Bundle.getMessage("SignalMastOperationType_NotHeld")),
425        PermissiveSmlDisabled(Bundle.getMessage("SignalMastOperationType_PermissiveSmlDisabled")),
426        PermissiveSmlNotDisabled(Bundle.getMessage("SignalMastOperationType_PermissiveSmlNotDisabled"));
427
428        private final String _text;
429
430        private OperationType(String text) {
431            this._text = text;
432        }
433
434        @Override
435        public String toString() {
436            return _text;
437        }
438
439    }
440
441    /** {@inheritDoc} */
442    @Override
443    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
444        log.debug("getUsageReport :: ActionSignalMast: bean = {}, report = {}", cdl, report);
445        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
446        _selectExampleNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Action);
447    }
448
449    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionSignalMast.class);
450
451}