001package jmri.jmrit.logixng.actions;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyVetoException;
005import java.beans.VetoableChangeListener;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Locale;
009import java.util.Map;
010
011import javax.annotation.Nonnull;
012
013import jmri.*;
014import jmri.jmrit.logixng.*;
015import jmri.jmrit.logixng.util.ReferenceUtil;
016import jmri.jmrit.logixng.util.parser.ExpressionNode;
017import jmri.jmrit.logixng.util.parser.ParserException;
018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
019import jmri.jmrit.logixng.util.parser.Variable;
020import jmri.util.ThreadingUtil;
021import jmri.util.TypeConversionUtil;
022
023/**
024 * This action sets the state of a light.
025 *
026 * @author Daniel Bergqvist Copyright 2018
027 */
028public class ActionLight extends AbstractDigitalAction implements VetoableChangeListener {
029
030    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
031    private NamedBeanHandle<Light> _lightHandle;
032    private String _reference = "";
033    private String _localVariable = "";
034    private String _formula = "";
035    private ExpressionNode _expressionNode;
036
037    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
038    private LightState _lightState = LightState.On;
039    private String _stateReference = "";
040    private String _stateLocalVariable = "";
041    private String _stateFormula = "";
042    private ExpressionNode _stateExpressionNode;
043
044    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
045    private String _dataReference = "";
046    private String _dataLocalVariable = "";
047    private String _dataFormula = "";
048    private ExpressionNode _dataExpressionNode;
049
050    private int _lightValue= 0;
051
052    public ActionLight(String sys, String user)
053            throws BadUserNameException, BadSystemNameException {
054        super(sys, user);
055    }
056
057    @Override
058    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
059        DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class);
060        String sysName = systemNames.get(getSystemName());
061        String userName = userNames.get(getSystemName());
062        if (sysName == null) sysName = manager.getAutoSystemName();
063        ActionLight copy = new ActionLight(sysName, userName);
064        copy.setComment(getComment());
065        if (_lightHandle != null) copy.setLight(_lightHandle);
066        copy.setBeanState(_lightState);
067        copy.setAddressing(_addressing);
068        copy.setFormula(_formula);
069        copy.setLocalVariable(_localVariable);
070        copy.setReference(_reference);
071
072        copy.setStateAddressing(_stateAddressing);
073        copy.setStateFormula(_stateFormula);
074        copy.setStateLocalVariable(_stateLocalVariable);
075        copy.setStateReference(_stateReference);
076
077        copy.setDataAddressing(_dataAddressing);
078        copy.setDataReference(_dataReference);
079        copy.setDataLocalVariable(_dataLocalVariable);
080        copy.setDataFormula(_dataFormula);
081
082        copy.setLightValue(_lightValue);
083
084        return manager.registerAction(copy);
085    }
086
087    public void setLight(@Nonnull String lightName) {
088        assertListenersAreNotRegistered(log, "setLight");
089        Light light = InstanceManager.getDefault(LightManager.class).getLight(lightName);
090        if (light != null) {
091            setLight(light);
092        } else {
093            removeLight();
094            log.warn("light \"{}\" is not found", lightName);
095        }
096    }
097
098    public void setLight(@Nonnull NamedBeanHandle<Light> handle) {
099        assertListenersAreNotRegistered(log, "setLight");
100        _lightHandle = handle;
101        InstanceManager.lightManagerInstance().addVetoableChangeListener(this);
102    }
103
104    public void setLight(@Nonnull Light light) {
105        assertListenersAreNotRegistered(log, "setLight");
106        setLight(InstanceManager.getDefault(NamedBeanHandleManager.class)
107                .getNamedBeanHandle(light.getDisplayName(), light));
108    }
109
110    public void removeLight() {
111        assertListenersAreNotRegistered(log, "setLight");
112        if (_lightHandle != null) {
113            InstanceManager.lightManagerInstance().removeVetoableChangeListener(this);
114            _lightHandle = null;
115        }
116    }
117
118    public NamedBeanHandle<Light> getLight() {
119        return _lightHandle;
120    }
121
122    public void setAddressing(NamedBeanAddressing addressing) throws ParserException {
123        _addressing = addressing;
124        parseFormula();
125    }
126
127    public NamedBeanAddressing getAddressing() {
128        return _addressing;
129    }
130
131    public void setReference(@Nonnull String reference) {
132        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
133            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
134        }
135        _reference = reference;
136    }
137
138    public String getReference() {
139        return _reference;
140    }
141
142    public void setLocalVariable(@Nonnull String localVariable) {
143        _localVariable = localVariable;
144    }
145
146    public String getLocalVariable() {
147        return _localVariable;
148    }
149
150    public void setFormula(@Nonnull String formula) throws ParserException {
151        _formula = formula;
152        parseFormula();
153    }
154
155    public String getFormula() {
156        return _formula;
157    }
158
159    private void parseFormula() throws ParserException {
160        if (_addressing == NamedBeanAddressing.Formula) {
161            Map<String, Variable> variables = new HashMap<>();
162
163            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
164            _expressionNode = parser.parseExpression(_formula);
165        } else {
166            _expressionNode = null;
167        }
168    }
169
170
171    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
172        _stateAddressing = addressing;
173        parseStateFormula();
174    }
175
176    public NamedBeanAddressing getStateAddressing() {
177        return _stateAddressing;
178    }
179
180    public void setBeanState(LightState state) {
181        _lightState = state;
182    }
183
184    public LightState getBeanState() {
185        return _lightState;
186    }
187
188    public void setStateReference(@Nonnull String reference) {
189        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
190            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
191        }
192        _stateReference = reference;
193    }
194
195    public String getStateReference() {
196        return _stateReference;
197    }
198
199    public void setStateLocalVariable(@Nonnull String localVariable) {
200        _stateLocalVariable = localVariable;
201    }
202
203    public String getStateLocalVariable() {
204        return _stateLocalVariable;
205    }
206
207    public void setStateFormula(@Nonnull String formula) throws ParserException {
208        _stateFormula = formula;
209        parseStateFormula();
210    }
211
212    public String getStateFormula() {
213        return _stateFormula;
214    }
215
216    private void parseStateFormula() throws ParserException {
217        if (_stateAddressing == NamedBeanAddressing.Formula) {
218            Map<String, Variable> variables = new HashMap<>();
219
220            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
221            _stateExpressionNode = parser.parseExpression(_stateFormula);
222        } else {
223            _stateExpressionNode = null;
224        }
225    }
226
227
228    public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
229        _dataAddressing = addressing;
230        parseDataFormula();
231    }
232
233    public NamedBeanAddressing getDataAddressing() {
234        return _dataAddressing;
235    }
236
237    public void setDataReference(@Nonnull String reference) {
238        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
239            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
240        }
241        _dataReference = reference;
242    }
243
244    public String getDataReference() {
245        return _dataReference;
246    }
247
248    public void setDataLocalVariable(@Nonnull String localVariable) {
249        _dataLocalVariable = localVariable;
250    }
251
252    public String getDataLocalVariable() {
253        return _dataLocalVariable;
254    }
255
256    public void setDataFormula(@Nonnull String formula) throws ParserException {
257        _dataFormula = formula;
258        parseDataFormula();
259    }
260
261    public String getDataFormula() {
262        return _dataFormula;
263    }
264
265    private void parseDataFormula() throws ParserException {
266        if (_dataAddressing == NamedBeanAddressing.Formula) {
267            Map<String, Variable> variables = new HashMap<>();
268
269            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
270            _dataExpressionNode = parser.parseExpression(_dataFormula);
271        } else {
272            _dataExpressionNode = null;
273        }
274    }
275
276
277    public void setLightValue(int value) {
278        _lightValue = value;
279    }
280
281    public int getLightValue() {
282        return _lightValue;
283    }
284
285
286    @Override
287    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
288        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
289            if (evt.getOldValue() instanceof Light) {
290                if (evt.getOldValue().equals(getLight().getBean())) {
291                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
292                    throw new PropertyVetoException(Bundle.getMessage("Light_LightInUseLightActionVeto", getDisplayName()), e); // NOI18N
293                }
294            }
295        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
296            if (evt.getOldValue() instanceof Light) {
297                if (evt.getOldValue().equals(getLight().getBean())) {
298                    removeLight();
299                }
300            }
301        }
302    }
303
304    /** {@inheritDoc} */
305    @Override
306    public Category getCategory() {
307        return Category.ITEM;
308    }
309
310    private String getNewState() throws JmriException {
311
312        switch (_stateAddressing) {
313            case Reference:
314                return ReferenceUtil.getReference(
315                        getConditionalNG().getSymbolTable(), _stateReference);
316
317            case LocalVariable:
318                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
319                return TypeConversionUtil
320                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
321
322            case Formula:
323                return _stateExpressionNode != null
324                        ? TypeConversionUtil.convertToString(
325                                _stateExpressionNode.calculate(
326                                        getConditionalNG().getSymbolTable()), false)
327                        : null;
328
329            default:
330                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
331        }
332    }
333
334    private int getNewData() throws JmriException {
335        String newValue = "";
336
337        switch (_dataAddressing) {
338            case Direct:
339                return _lightValue;
340
341            case Reference:
342                newValue = ReferenceUtil.getReference(
343                        getConditionalNG().getSymbolTable(), _dataReference);
344                break;
345
346            case LocalVariable:
347                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
348                newValue = TypeConversionUtil
349                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
350                break;
351
352            case Formula:
353                newValue = _dataExpressionNode != null
354                        ? TypeConversionUtil.convertToString(
355                                _dataExpressionNode.calculate(
356                                        getConditionalNG().getSymbolTable()), false)
357                        : "";
358                break;
359
360            default:
361                throw new IllegalArgumentException("invalid _addressing state: " + _dataAddressing.name());
362        }
363        try {
364            int newInt = Integer.parseInt(newValue);
365            if (newInt < 0) newInt = 0;
366            if (newInt > 100) newInt = 100;
367            return newInt;
368        } catch (NumberFormatException ex) {
369            return 0;
370        }
371    }
372
373    /** {@inheritDoc} */
374    @Override
375    public void execute() throws JmriException {
376        Light light;
377
378        switch (_addressing) {
379            case Direct:
380                light = _lightHandle != null ? _lightHandle.getBean() : null;
381                break;
382
383            case Reference:
384                String ref = ReferenceUtil.getReference(
385                        getConditionalNG().getSymbolTable(), _reference);
386                light = InstanceManager.getDefault(LightManager.class)
387                        .getNamedBean(ref);
388                break;
389
390            case LocalVariable:
391                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
392                light = InstanceManager.getDefault(LightManager.class)
393                        .getNamedBean(TypeConversionUtil
394                                .convertToString(symbolTable.getValue(_localVariable), false));
395                break;
396
397            case Formula:
398                light = _expressionNode != null ?
399                        InstanceManager.getDefault(LightManager.class)
400                                .getNamedBean(TypeConversionUtil
401                                        .convertToString(_expressionNode.calculate(
402                                                getConditionalNG().getSymbolTable()), false))
403                        : null;
404                break;
405
406            default:
407                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
408        }
409
410        if (light == null) {
411//            log.warn("light is null");
412            return;
413        }
414
415        String name = (_stateAddressing != NamedBeanAddressing.Direct)
416                ? getNewState() : null;
417
418        LightState state;
419        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
420            state = _lightState;
421        } else {
422            state = LightState.valueOf(name);
423        }
424
425        ThreadingUtil.runOnLayoutWithJmriException(() -> {
426            if (state == LightState.Toggle) {
427                if (light.getCommandedState() == Turnout.CLOSED) {
428                    light.setCommandedState(Turnout.THROWN);
429                } else {
430                    light.setCommandedState(Turnout.CLOSED);
431                }
432
433            } else if (state == LightState.Intensity) {
434                if (light instanceof VariableLight) {
435                    ((VariableLight)light).setTargetIntensity(getNewData() / 100.0);
436                } else {
437                    light.setCommandedState(getNewData() > 50 ? Light.ON : Light.OFF);
438                }
439            } else if (state == LightState.Interval) {
440                if (light instanceof VariableLight) {
441                    ((VariableLight)light).setTransitionTime(getNewData());
442                }
443            } else {
444                light.setCommandedState(state.getID());
445            }
446        });
447    }
448
449    @Override
450    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
451        throw new UnsupportedOperationException("Not supported.");
452    }
453
454    @Override
455    public int getChildCount() {
456        return 0;
457    }
458
459    @Override
460    public String getShortDescription(Locale locale) {
461        return Bundle.getMessage(locale, "Light_Short");
462    }
463
464    @Override
465    public String getLongDescription(Locale locale) {
466        String namedBean;
467        String state;
468
469        switch (_addressing) {
470            case Direct:
471                String lightName;
472                if (_lightHandle != null) {
473                    lightName = _lightHandle.getBean().getDisplayName();
474                } else {
475                    lightName = Bundle.getMessage(locale, "BeanNotSelected");
476                }
477                namedBean = Bundle.getMessage(locale, "AddressByDirect", lightName);
478                break;
479
480            case Reference:
481                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
482                break;
483
484            case LocalVariable:
485                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
486                break;
487
488            case Formula:
489                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
490                break;
491
492            default:
493                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
494        }
495
496        switch (_stateAddressing) {
497            case Direct:
498                if (_lightState == LightState.Intensity || _lightState == LightState.Interval) {
499                    String bundleKey = "Light_Long_Value";
500                    switch (_dataAddressing) {
501                        case Direct:
502                            String type = _lightState == LightState.Intensity ?
503                                     Bundle.getMessage("Light_Intensity_Value") :
504                                     Bundle.getMessage("Light_Interval_Value");
505                            return Bundle.getMessage(locale, bundleKey, namedBean, type, _lightValue);
506                        case Reference:
507                            return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByReference", _dataReference));
508                        case LocalVariable:
509                            return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
510                        case Formula:
511                            return Bundle.getMessage(locale, bundleKey, namedBean, "", Bundle.getMessage("AddressByFormula", _dataFormula));
512                        default:
513                            throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
514                    }
515                } else {
516                    state = Bundle.getMessage(locale, "AddressByDirect", _lightState._text);
517                }
518                break;
519
520            case Reference:
521                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
522                break;
523
524            case LocalVariable:
525                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
526                break;
527
528            case Formula:
529                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
530                break;
531
532            default:
533                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
534        }
535
536        return Bundle.getMessage(locale, "Light_Long", namedBean, state);
537    }
538
539    /** {@inheritDoc} */
540    @Override
541    public void setup() {
542        // Do nothing
543    }
544
545    /** {@inheritDoc} */
546    @Override
547    public void registerListenersForThisClass() {
548    }
549
550    /** {@inheritDoc} */
551    @Override
552    public void unregisterListenersForThisClass() {
553    }
554
555    /** {@inheritDoc} */
556    @Override
557    public void disposeMe() {
558    }
559
560
561    // This constant is only used internally in LightState but must be outside
562    // the enum.
563    private static final int TOGGLE_ID = -1;
564    private static final int INTENSITY_ID = -2;
565    private static final int INTERVAL_ID = -3;
566
567
568    public enum LightState {
569        Off(Light.OFF, Bundle.getMessage("StateOff")),
570        On(Light.ON, Bundle.getMessage("StateOn")),
571        Toggle(TOGGLE_ID, Bundle.getMessage("LightToggleStatus")),
572        Intensity(INTENSITY_ID, Bundle.getMessage("LightIntensity")),
573        Interval(INTERVAL_ID, Bundle.getMessage("LightInterval"));
574
575        private final int _id;
576        private final String _text;
577
578        private LightState(int id, String text) {
579            this._id = id;
580            this._text = text;
581        }
582
583        static public LightState get(int id) {
584            switch (id) {
585                case Light.OFF:
586                    return Off;
587
588                case Light.ON:
589                    return On;
590
591                case TOGGLE_ID:
592                    return Toggle;
593
594                default:
595                    throw new IllegalArgumentException("invalid light state");
596            }
597        }
598
599        public int getID() {
600            return _id;
601        }
602
603        @Override
604        public String toString() {
605            return _text;
606        }
607
608    }
609
610    /** {@inheritDoc} */
611    @Override
612    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
613        log.debug("getUsageReport :: ActionLight: bean = {}, report = {}", cdl, report);
614        if (getLight() != null && bean.equals(getLight().getBean())) {
615            report.add(new NamedBeanUsageReport("LogixNGAction", cdl, getLongDescription()));
616        }
617    }
618
619    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionLight.class);
620
621}