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