001package jmri.jmrit.logixng.expressions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.jmrit.logixng.*;
010import jmri.jmrit.logixng.util.ReferenceUtil;
011import jmri.jmrit.logixng.util.parser.*;
012import jmri.jmrit.logixng.util.parser.ExpressionNode;
013import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
014import jmri.util.TypeConversionUtil;
015
016/**
017 * This expression sets the state of a light.
018 *
019 * @author Daniel Bergqvist Copyright 2018
020 */
021public class ExpressionLight extends AbstractDigitalExpression
022        implements PropertyChangeListener, VetoableChangeListener {
023
024    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
025    private NamedBeanHandle<Light> _lightHandle;
026    private String _reference = "";
027    private String _localVariable = "";
028    private String _formula = "";
029    private ExpressionNode _expressionNode;
030    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
031    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
032    private LightState _lightState = LightState.On;
033    private String _stateReference = "";
034    private String _stateLocalVariable = "";
035    private String _stateFormula = "";
036    private ExpressionNode _stateExpressionNode;
037
038    public ExpressionLight(String sys, String user)
039            throws BadUserNameException, BadSystemNameException {
040        super(sys, user);
041    }
042
043    @Override
044    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
045        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
046        String sysName = systemNames.get(getSystemName());
047        String userName = userNames.get(getSystemName());
048        if (sysName == null) sysName = manager.getAutoSystemName();
049        ExpressionLight copy = new ExpressionLight(sysName, userName);
050        copy.setComment(getComment());
051        if (_lightHandle != null) copy.setLight(_lightHandle);
052        copy.setBeanState(_lightState);
053        copy.setAddressing(_addressing);
054        copy.setFormula(_formula);
055        copy.setLocalVariable(_localVariable);
056        copy.setReference(_reference);
057        copy.set_Is_IsNot(_is_IsNot);
058        copy.setStateAddressing(_stateAddressing);
059        copy.setStateFormula(_stateFormula);
060        copy.setStateLocalVariable(_stateLocalVariable);
061        copy.setStateReference(_stateReference);
062        return manager.registerExpression(copy);
063    }
064
065    public void setLight(@Nonnull String lightName) {
066        assertListenersAreNotRegistered(log, "setLight");
067        Light light = InstanceManager.getDefault(LightManager.class).getLight(lightName);
068        if (light != null) {
069            setLight(light);
070        } else {
071            removeLight();
072            log.error("light \"{}\" is not found", lightName);
073        }
074    }
075
076    public void setLight(@Nonnull NamedBeanHandle<Light> handle) {
077        assertListenersAreNotRegistered(log, "setLight");
078        _lightHandle = handle;
079        InstanceManager.lightManagerInstance().addVetoableChangeListener(this);
080    }
081
082    public void setLight(@Nonnull Light light) {
083        assertListenersAreNotRegistered(log, "setLight");
084        setLight(InstanceManager.getDefault(NamedBeanHandleManager.class)
085                .getNamedBeanHandle(light.getDisplayName(), light));
086    }
087
088    public void removeLight() {
089        assertListenersAreNotRegistered(log, "setLight");
090        if (_lightHandle != null) {
091            InstanceManager.lightManagerInstance().removeVetoableChangeListener(this);
092            _lightHandle = null;
093        }
094    }
095
096    public NamedBeanHandle<Light> getLight() {
097        return _lightHandle;
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 set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
149        _is_IsNot = is_IsNot;
150    }
151
152    public Is_IsNot_Enum get_Is_IsNot() {
153        return _is_IsNot;
154    }
155
156    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
157        _stateAddressing = addressing;
158        parseStateFormula();
159    }
160
161    public NamedBeanAddressing getStateAddressing() {
162        return _stateAddressing;
163    }
164
165    public void setBeanState(LightState state) {
166        _lightState = state;
167    }
168
169    public LightState getBeanState() {
170        return _lightState;
171    }
172
173    public void setStateReference(@Nonnull String reference) {
174        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
175            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
176        }
177        _stateReference = reference;
178    }
179
180    public String getStateReference() {
181        return _stateReference;
182    }
183
184    public void setStateLocalVariable(@Nonnull String localVariable) {
185        _stateLocalVariable = localVariable;
186    }
187
188    public String getStateLocalVariable() {
189        return _stateLocalVariable;
190    }
191
192    public void setStateFormula(@Nonnull String formula) throws ParserException {
193        _stateFormula = formula;
194        parseStateFormula();
195    }
196
197    public String getStateFormula() {
198        return _stateFormula;
199    }
200
201    private void parseStateFormula() throws ParserException {
202        if (_stateAddressing == NamedBeanAddressing.Formula) {
203            Map<String, Variable> variables = new HashMap<>();
204
205            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
206            _stateExpressionNode = parser.parseExpression(_stateFormula);
207        } else {
208            _stateExpressionNode = null;
209        }
210    }
211
212    @Override
213    public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException {
214        if ("CanDelete".equals(evt.getPropertyName())) { // No I18N
215            if (evt.getOldValue() instanceof Light) {
216                if (evt.getOldValue().equals(getLight().getBean())) {
217                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
218                    throw new PropertyVetoException(Bundle.getMessage("Light_LightInUseLightExpressionVeto", getDisplayName()), e); // NOI18N
219                }
220            }
221        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
222            if (evt.getOldValue() instanceof Light) {
223                if (evt.getOldValue().equals(getLight().getBean())) {
224                    removeLight();
225                }
226            }
227        }
228    }
229
230    /** {@inheritDoc} */
231    @Override
232    public Category getCategory() {
233        return Category.ITEM;
234    }
235
236    private String getNewState() throws JmriException {
237
238        switch (_stateAddressing) {
239            case Reference:
240                return ReferenceUtil.getReference(
241                        getConditionalNG().getSymbolTable(), _stateReference);
242
243            case LocalVariable:
244                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
245                return TypeConversionUtil
246                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
247
248            case Formula:
249                return _stateExpressionNode != null
250                        ? TypeConversionUtil.convertToString(
251                                _stateExpressionNode.calculate(
252                                        getConditionalNG().getSymbolTable()), false)
253                        : null;
254
255            default:
256                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
257        }
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public boolean evaluate() throws JmriException {
263        Light light;
264
265//        System.out.format("ExpressionLight.execute: %s%n", getLongDescription());
266
267        switch (_addressing) {
268            case Direct:
269                light = _lightHandle != null ? _lightHandle.getBean() : null;
270                break;
271
272            case Reference:
273                String ref = ReferenceUtil.getReference(
274                        getConditionalNG().getSymbolTable(), _reference);
275                light = InstanceManager.getDefault(LightManager.class)
276                        .getNamedBean(ref);
277                break;
278
279            case LocalVariable:
280                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
281                light = InstanceManager.getDefault(LightManager.class)
282                        .getNamedBean(TypeConversionUtil
283                                .convertToString(symbolTable.getValue(_localVariable), false));
284                break;
285
286            case Formula:
287                light = _expressionNode != null ?
288                        InstanceManager.getDefault(LightManager.class)
289                                .getNamedBean(TypeConversionUtil
290                                        .convertToString(_expressionNode.calculate(
291                                                getConditionalNG().getSymbolTable()), false))
292                        : null;
293                break;
294
295            default:
296                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
297        }
298
299//        System.out.format("ExpressionLight.execute: light: %s%n", light);
300
301        if (light == null) {
302//            log.warn("light is null");
303            return false;
304        }
305
306        LightState checkLightState;
307
308        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
309            checkLightState = _lightState;
310        } else {
311            checkLightState = LightState.valueOf(getNewState());
312        }
313
314        LightState currentLightState = LightState.get(light.getCommandedState());
315        if (_is_IsNot == Is_IsNot_Enum.Is) {
316            return currentLightState == checkLightState;
317        } else {
318            return currentLightState != checkLightState;
319        }
320    }
321
322    @Override
323    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
324        throw new UnsupportedOperationException("Not supported.");
325    }
326
327    @Override
328    public int getChildCount() {
329        return 0;
330    }
331
332    @Override
333    public String getShortDescription(Locale locale) {
334        return Bundle.getMessage(locale, "Light_Short");
335    }
336
337    @Override
338    public String getLongDescription(Locale locale) {
339        String namedBean;
340        String state;
341
342        switch (_addressing) {
343            case Direct:
344                String lightName;
345                if (_lightHandle != null) {
346                    lightName = _lightHandle.getBean().getDisplayName();
347                } else {
348                    lightName = Bundle.getMessage(locale, "BeanNotSelected");
349                }
350                namedBean = Bundle.getMessage(locale, "AddressByDirect", lightName);
351                break;
352
353            case Reference:
354                namedBean = Bundle.getMessage(locale, "AddressByReference", _reference);
355                break;
356
357            case LocalVariable:
358                namedBean = Bundle.getMessage(locale, "AddressByLocalVariable", _localVariable);
359                break;
360
361            case Formula:
362                namedBean = Bundle.getMessage(locale, "AddressByFormula", _formula);
363                break;
364
365            default:
366                throw new IllegalArgumentException("invalid _addressing state: " + _addressing.name());
367        }
368
369        switch (_stateAddressing) {
370            case Direct:
371                state = Bundle.getMessage(locale, "AddressByDirect", _lightState._text);
372                break;
373
374            case Reference:
375                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
376                break;
377
378            case LocalVariable:
379                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
380                break;
381
382            case Formula:
383                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
384                break;
385
386            default:
387                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
388        }
389
390        return Bundle.getMessage(locale, "Light_Long", namedBean, _is_IsNot.toString(), state);
391    }
392
393    /** {@inheritDoc} */
394    @Override
395    public void setup() {
396        // Do nothing
397    }
398
399    /** {@inheritDoc} */
400    @Override
401    public void registerListenersForThisClass() {
402        if (!_listenersAreRegistered && (_lightHandle != null)) {
403            _lightHandle.getBean().addPropertyChangeListener("KnownState", this);
404            _listenersAreRegistered = true;
405        }
406    }
407
408    /** {@inheritDoc} */
409    @Override
410    public void unregisterListenersForThisClass() {
411        if (_listenersAreRegistered) {
412            _lightHandle.getBean().removePropertyChangeListener("KnownState", this);
413            _listenersAreRegistered = false;
414        }
415    }
416
417    /** {@inheritDoc} */
418    @Override
419    public void propertyChange(PropertyChangeEvent evt) {
420        getConditionalNG().execute();
421    }
422
423    /** {@inheritDoc} */
424    @Override
425    public void disposeMe() {
426    }
427
428
429    public enum LightState {
430        Off(Light.OFF, Bundle.getMessage("StateOff")),
431        On(Light.ON, Bundle.getMessage("StateOn")),
432        Other(-1, Bundle.getMessage("SensorOtherStatus"));
433
434        private final int _id;
435        private final String _text;
436
437        private LightState(int id, String text) {
438            this._id = id;
439            this._text = text;
440        }
441
442        static public LightState get(int id) {
443            switch (id) {
444                case Light.OFF:
445                    return Off;
446
447                case Light.ON:
448                    return On;
449
450                default:
451                    return Other;
452            }
453        }
454
455        public int getID() {
456            return _id;
457        }
458
459        @Override
460        public String toString() {
461            return _text;
462        }
463
464    }
465
466
467    /** {@inheritDoc} */
468    @Override
469    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
470        log.debug("getUsageReport :: ExpressionLight: bean = {}, report = {}", cdl, report);
471        if (getLight() != null && bean.equals(getLight().getBean())) {
472            report.add(new NamedBeanUsageReport("LogixNGExpression", cdl, getLongDescription()));
473        }
474    }
475    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionLight.class);
476
477}