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 sensor.
018 *
019 * @author Daniel Bergqvist Copyright 2018
020 */
021public class ExpressionSensor extends AbstractDigitalExpression
022        implements PropertyChangeListener, VetoableChangeListener {
023
024    private NamedBeanAddressing _addressing = NamedBeanAddressing.Direct;
025    private NamedBeanHandle<Sensor> _sensorHandle;
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 SensorState _sensorState = SensorState.Active;
033    private String _stateReference = "";
034    private String _stateLocalVariable = "";
035    private String _stateFormula = "";
036    private ExpressionNode _stateExpressionNode;
037
038    public ExpressionSensor(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        ExpressionSensor copy = new ExpressionSensor(sysName, userName);
050        copy.setComment(getComment());
051        if (_sensorHandle != null) copy.setSensor(_sensorHandle);
052        copy.setBeanState(_sensorState);
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 setSensor(@Nonnull String sensorName) {
066        assertListenersAreNotRegistered(log, "setSensor");
067        Sensor sensor = InstanceManager.getDefault(SensorManager.class).getSensor(sensorName);
068        if (sensor != null) {
069            setSensor(sensor);
070        } else {
071            removeSensor();
072            log.error("sensor \"{}\" is not found", sensorName);
073        }
074    }
075
076    public void setSensor(@Nonnull NamedBeanHandle<Sensor> handle) {
077        assertListenersAreNotRegistered(log, "setSensor");
078        _sensorHandle = handle;
079        InstanceManager.sensorManagerInstance().addVetoableChangeListener(this);
080    }
081
082    public void setSensor(@Nonnull Sensor sensor) {
083        assertListenersAreNotRegistered(log, "setSensor");
084        setSensor(InstanceManager.getDefault(NamedBeanHandleManager.class)
085                .getNamedBeanHandle(sensor.getDisplayName(), sensor));
086    }
087
088    public void removeSensor() {
089        assertListenersAreNotRegistered(log, "setSensor");
090        if (_sensorHandle != null) {
091            InstanceManager.sensorManagerInstance().removeVetoableChangeListener(this);
092            _sensorHandle = null;
093        }
094    }
095
096    public NamedBeanHandle<Sensor> getSensor() {
097        return _sensorHandle;
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(SensorState state) {
166        _sensorState = state;
167    }
168
169    public SensorState getBeanState() {
170        return _sensorState;
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 Sensor) {
216                if (evt.getOldValue().equals(getSensor().getBean())) {
217                    PropertyChangeEvent e = new PropertyChangeEvent(this, "DoNotDelete", null, null);
218                    throw new PropertyVetoException(Bundle.getMessage("Sensor_SensorInUseSensorExpressionVeto", getDisplayName()), e); // NOI18N
219                }
220            }
221        } else if ("DoDelete".equals(evt.getPropertyName())) { // No I18N
222            if (evt.getOldValue() instanceof Sensor) {
223                if (evt.getOldValue().equals(getSensor().getBean())) {
224                    removeSensor();
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        Sensor sensor;
264
265//        System.out.format("ExpressionSensor.execute: %s%n", getLongDescription());
266
267        switch (_addressing) {
268            case Direct:
269                sensor = _sensorHandle != null ? _sensorHandle.getBean() : null;
270                break;
271
272            case Reference:
273                String ref = ReferenceUtil.getReference(
274                        getConditionalNG().getSymbolTable(), _reference);
275                sensor = InstanceManager.getDefault(SensorManager.class)
276                        .getNamedBean(ref);
277                break;
278
279            case LocalVariable:
280                SymbolTable symbolTable = getConditionalNG().getSymbolTable();
281                sensor = InstanceManager.getDefault(SensorManager.class)
282                        .getNamedBean(TypeConversionUtil
283                                .convertToString(symbolTable.getValue(_localVariable), false));
284                break;
285
286            case Formula:
287                sensor = _expressionNode != null ?
288                        InstanceManager.getDefault(SensorManager.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("ExpressionSensor.execute: sensor: %s%n", sensor);
300
301        if (sensor == null) {
302//            log.warn("sensor is null");
303            return false;
304        }
305
306        SensorState checkSensorState;
307
308        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
309            checkSensorState = _sensorState;
310        } else {
311            checkSensorState = SensorState.valueOf(getNewState());
312        }
313
314        SensorState currentSensorState = SensorState.get(sensor.getCommandedState());
315        if (_is_IsNot == Is_IsNot_Enum.Is) {
316            return currentSensorState == checkSensorState;
317        } else {
318            return currentSensorState != checkSensorState;
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, "Sensor_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 sensorName;
345                if (_sensorHandle != null) {
346                    sensorName = _sensorHandle.getBean().getDisplayName();
347                } else {
348                    sensorName = Bundle.getMessage(locale, "BeanNotSelected");
349                }
350                namedBean = Bundle.getMessage(locale, "AddressByDirect", sensorName);
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", _sensorState._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, "Sensor_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 && (_sensorHandle != null)) {
403            _sensorHandle.getBean().addPropertyChangeListener("KnownState", this);
404            _listenersAreRegistered = true;
405        }
406    }
407
408    /** {@inheritDoc} */
409    @Override
410    public void unregisterListenersForThisClass() {
411        if (_listenersAreRegistered) {
412            _sensorHandle.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 SensorState {
430        Inactive(Sensor.INACTIVE, Bundle.getMessage("SensorStateInactive")),
431        Active(Sensor.ACTIVE, Bundle.getMessage("SensorStateActive")),
432        Other(-1, Bundle.getMessage("SensorOtherStatus"));
433
434        private final int _id;
435        private final String _text;
436
437        private SensorState(int id, String text) {
438            this._id = id;
439            this._text = text;
440        }
441
442        static public SensorState get(int id) {
443            switch (id) {
444                case Sensor.INACTIVE:
445                    return Inactive;
446
447                case Sensor.ACTIVE:
448                    return Active;
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    /** {@inheritDoc} */
467    @Override
468    public void getUsageDetail(int level, NamedBean bean, List<NamedBeanUsageReport> report, NamedBean cdl) {
469        log.debug("getUsageReport :: ExpressionBlock: bean = {}, report = {}", cdl, report);
470        if (getSensor() != null && bean.equals(getSensor().getBean())) {
471            report.add(new NamedBeanUsageReport("LogixNGExpression", cdl, getLongDescription()));
472        }
473    }
474
475    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionSensor.class);
476}