001package jmri.jmrit.logixng.expressions;
002
003import java.beans.*;
004import java.util.*;
005
006import javax.annotation.Nonnull;
007
008import jmri.*;
009import jmri.Block;
010import jmri.BlockManager;
011import jmri.jmrit.display.layoutEditor.LayoutBlock;
012import jmri.jmrit.display.layoutEditor.LayoutBlockManager;
013import jmri.jmrit.logixng.*;
014import jmri.jmrit.logixng.util.LogixNG_SelectNamedBean;
015import jmri.jmrit.logixng.util.ReferenceUtil;
016import jmri.jmrit.logixng.util.parser.*;
017import jmri.jmrit.logixng.util.parser.ExpressionNode;
018import jmri.jmrit.logixng.util.parser.RecursiveDescentParser;
019import jmri.util.TypeConversionUtil;
020
021/**
022 * This expression evaluates the state of a Block.
023 * The supported characteristics are:
024 * <ul>
025 *   <li>Is [not] Occupied (based on occupancy sensor state)</li>
026 *   <li>Is [not] Unoccupied (based on occupancy sensor state)</li>
027 *   <li>Is [not] Other (UNKNOWN, INCONSISTENT, UNDETECTED)</li>
028 *   <li>Is [not] Allocated (based on the LayoutBlock useAlternateColor)</li>
029 *   <li>Value [not] equals string</li>
030 * </ul>
031 * @author Daniel Bergqvist Copyright 2021
032 * @author Dave Sand Copyright 2021
033 */
034public class ExpressionBlock extends AbstractDigitalExpression
035        implements PropertyChangeListener {
036
037    private final LogixNG_SelectNamedBean<Block> _selectNamedBean =
038            new LogixNG_SelectNamedBean<>(
039                    this, Block.class, InstanceManager.getDefault(BlockManager.class), this);
040
041    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
042
043    private NamedBeanAddressing _stateAddressing = NamedBeanAddressing.Direct;
044    private BlockState _blockState = BlockState.Occupied;
045    private String _stateReference = "";
046    private String _stateLocalVariable = "";
047    private String _stateFormula = "";
048    private ExpressionNode _stateExpressionNode;
049
050    private NamedBeanAddressing _dataAddressing = NamedBeanAddressing.Direct;
051    private String _dataReference = "";
052    private String _dataLocalVariable = "";
053    private String _dataFormula = "";
054    private ExpressionNode _dataExpressionNode;
055
056    private String _blockValue = "";
057
058    public ExpressionBlock(String sys, String user)
059            throws BadUserNameException, BadSystemNameException {
060        super(sys, user);
061    }
062
063    @Override
064    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
065        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
066        String sysName = systemNames.get(getSystemName());
067        String userName = userNames.get(getSystemName());
068        if (sysName == null) sysName = manager.getAutoSystemName();
069        ExpressionBlock copy = new ExpressionBlock(sysName, userName);
070        copy.setComment(getComment());
071
072        _selectNamedBean.copy(copy._selectNamedBean);
073
074        copy.set_Is_IsNot(_is_IsNot);
075
076        copy.setStateAddressing(_stateAddressing);
077        copy.setBeanState(_blockState);
078        copy.setStateReference(_stateReference);
079        copy.setStateLocalVariable(_stateLocalVariable);
080        copy.setStateFormula(_stateFormula);
081
082        copy.setDataAddressing(_dataAddressing);
083        copy.setDataReference(_dataReference);
084        copy.setDataLocalVariable(_dataLocalVariable);
085        copy.setDataFormula(_dataFormula);
086        copy.setBlockValue(_blockValue);
087        return manager.registerExpression(copy);
088    }
089
090    public LogixNG_SelectNamedBean<Block> getSelectNamedBean() {
091        return _selectNamedBean;
092    }
093
094    public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
095        _is_IsNot = is_IsNot;
096    }
097
098    public Is_IsNot_Enum get_Is_IsNot() {
099        return _is_IsNot;
100    }
101
102
103    public void setStateAddressing(NamedBeanAddressing addressing) throws ParserException {
104        _stateAddressing = addressing;
105        parseStateFormula();
106    }
107
108    public NamedBeanAddressing getStateAddressing() {
109        return _stateAddressing;
110    }
111
112    public void setBeanState(BlockState state) {
113        _blockState = state;
114    }
115
116    public BlockState getBeanState() {
117        return _blockState;
118    }
119
120    public void setStateReference(@Nonnull String reference) {
121        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
122            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
123        }
124        _stateReference = reference;
125    }
126
127    public String getStateReference() {
128        return _stateReference;
129    }
130
131    public void setStateLocalVariable(@Nonnull String localVariable) {
132        _stateLocalVariable = localVariable;
133    }
134
135    public String getStateLocalVariable() {
136        return _stateLocalVariable;
137    }
138
139    public void setStateFormula(@Nonnull String formula) throws ParserException {
140        _stateFormula = formula;
141        parseStateFormula();
142    }
143
144    public String getStateFormula() {
145        return _stateFormula;
146    }
147
148    private void parseStateFormula() throws ParserException {
149        if (_stateAddressing == NamedBeanAddressing.Formula) {
150            Map<String, Variable> variables = new HashMap<>();
151
152            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
153            _stateExpressionNode = parser.parseExpression(_stateFormula);
154        } else {
155            _stateExpressionNode = null;
156        }
157    }
158
159
160    public void setDataAddressing(NamedBeanAddressing addressing) throws ParserException {
161        _dataAddressing = addressing;
162        parseDataFormula();
163    }
164
165    public NamedBeanAddressing getDataAddressing() {
166        return _dataAddressing;
167    }
168
169    public void setDataReference(@Nonnull String reference) {
170        if ((! reference.isEmpty()) && (! ReferenceUtil.isReference(reference))) {
171            throw new IllegalArgumentException("The reference \"" + reference + "\" is not a valid reference");
172        }
173        _dataReference = reference;
174    }
175
176    public String getDataReference() {
177        return _dataReference;
178    }
179
180    public void setDataLocalVariable(@Nonnull String localVariable) {
181        _dataLocalVariable = localVariable;
182    }
183
184    public String getDataLocalVariable() {
185        return _dataLocalVariable;
186    }
187
188    public void setDataFormula(@Nonnull String formula) throws ParserException {
189        _dataFormula = formula;
190        parseDataFormula();
191    }
192
193    public String getDataFormula() {
194        return _dataFormula;
195    }
196
197    private void parseDataFormula() throws ParserException {
198        if (_dataAddressing == NamedBeanAddressing.Formula) {
199            Map<String, Variable> variables = new HashMap<>();
200
201            RecursiveDescentParser parser = new RecursiveDescentParser(variables);
202            _dataExpressionNode = parser.parseExpression(_dataFormula);
203        } else {
204            _dataExpressionNode = null;
205        }
206    }
207
208
209    public void setBlockValue(@Nonnull String value) {
210        _blockValue = value;
211    }
212
213    public String getBlockValue() {
214        return _blockValue;
215    }
216
217    /** {@inheritDoc} */
218    @Override
219    public Category getCategory() {
220        return Category.ITEM;
221    }
222
223    private String getNewState() throws JmriException {
224
225        switch (_stateAddressing) {
226            case Reference:
227                return ReferenceUtil.getReference(
228                        getConditionalNG().getSymbolTable(), _stateReference);
229
230            case LocalVariable:
231                SymbolTable symbolTable =
232                        getConditionalNG().getSymbolTable();
233                return TypeConversionUtil
234                        .convertToString(symbolTable.getValue(_stateLocalVariable), false);
235
236            case Formula:
237                return _stateExpressionNode != null
238                        ? TypeConversionUtil.convertToString(
239                                _stateExpressionNode.calculate(
240                                        getConditionalNG().getSymbolTable()), false)
241                        : null;
242
243            default:
244                throw new IllegalArgumentException("invalid _addressing state: " + _stateAddressing.name());
245        }
246    }
247
248    private String getNewData() throws JmriException {
249
250        switch (_dataAddressing) {
251            case Reference:
252                return ReferenceUtil.getReference(
253                        getConditionalNG().getSymbolTable(), _dataReference);
254
255            case LocalVariable:
256                SymbolTable symbolTable =
257                        getConditionalNG().getSymbolTable();
258                return TypeConversionUtil
259                        .convertToString(symbolTable.getValue(_dataLocalVariable), false);
260
261            case Formula:
262                return _dataExpressionNode != null
263                        ? TypeConversionUtil.convertToString(
264                                _dataExpressionNode.calculate(
265                                        getConditionalNG().getSymbolTable()), false)
266                        : null;
267
268            default:
269                throw new IllegalArgumentException("invalid _addressing state: " + _dataAddressing.name());
270        }
271    }
272
273    /**
274     * A block is considered to be allocated if the related layout block has use extra color enabled.
275     * @param block The block whose allocation state is requested.
276     * @return true if the layout block is using the extra color.
277     */
278    public boolean isBlockAllocated(Block block) {
279        boolean result = false;
280        LayoutBlock layoutBlock = InstanceManager.getDefault(LayoutBlockManager.class).getLayoutBlock(block);
281        if (layoutBlock != null) {
282            result = layoutBlock.getUseExtraColor();
283        }
284        return result;
285    }
286
287    /** {@inheritDoc} */
288    @Override
289    public boolean evaluate() throws JmriException {
290        Block block = _selectNamedBean.evaluateNamedBean(getConditionalNG());
291
292        if (block == null) return false;
293
294        BlockState checkBlockState;
295
296        if ((_stateAddressing == NamedBeanAddressing.Direct)) {
297            checkBlockState = _blockState;
298        } else {
299            checkBlockState = BlockState.valueOf(getNewState());
300        }
301
302        int currentState = block.getState();
303        Object currentValue = null;
304
305        switch (checkBlockState) {
306            case Other:
307                if (currentState != Block.OCCUPIED && currentState != Block.UNOCCUPIED) {
308                    currentState = BlockState.Other.getID();
309                } else {
310                    currentState = 0;
311                }
312                break;
313
314            case Allocated:
315                boolean cuurrentAllocation = isBlockAllocated(block);
316                currentState = cuurrentAllocation ? BlockState.Allocated.getID() : 0;
317                break;
318
319            case ValueMatches:
320                currentValue = block.getValue();
321                if (_dataAddressing == NamedBeanAddressing.Direct) {
322                    currentState = _blockValue.equals(currentValue) ? BlockState.ValueMatches.getID() : 0;
323                } else {
324                    currentState = getNewData().equals(currentValue) ? BlockState.ValueMatches.getID() : 0;
325                }
326                break;
327
328            default:
329                break;
330        }
331
332        if (_is_IsNot == Is_IsNot_Enum.Is) {
333            return currentState == checkBlockState.getID();
334        } else {
335            return currentState != checkBlockState.getID();
336        }
337    }
338
339    @Override
340    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
341        throw new UnsupportedOperationException("Not supported.");
342    }
343
344    @Override
345    public int getChildCount() {
346        return 0;
347    }
348
349    @Override
350    public String getShortDescription(Locale locale) {
351        return Bundle.getMessage(locale, "Block_Short");
352    }
353
354    @Override
355    public String getLongDescription(Locale locale) {
356        String namedBean = _selectNamedBean.getDescription(locale);
357        String state;
358
359        switch (_stateAddressing) {
360            case Direct:
361                if (_blockState == BlockState.ValueMatches) {
362                    String bundleKey = "Block_Long_Value";
363                    String equalsString = _is_IsNot == Is_IsNot_Enum.Is ? Bundle.getMessage("Block_Equal") : Bundle.getMessage("Block_NotEqual");
364                    switch (_dataAddressing) {
365                        case Direct:
366                            return Bundle.getMessage(locale, bundleKey, namedBean, equalsString, _blockValue);
367                        case Reference:
368                            return Bundle.getMessage(locale, bundleKey, namedBean, equalsString, Bundle.getMessage("AddressByReference", _dataReference));
369                        case LocalVariable:
370                            return Bundle.getMessage(locale, bundleKey, namedBean, equalsString, Bundle.getMessage("AddressByLocalVariable", _dataLocalVariable));
371                        case Formula:
372                            return Bundle.getMessage(locale, bundleKey, namedBean, equalsString, Bundle.getMessage("AddressByFormula", _dataFormula));
373                        default:
374                            throw new IllegalArgumentException("invalid _dataAddressing state: " + _dataAddressing.name());
375                    }
376                } else if (_blockState == BlockState.Other) {
377                    state = Bundle.getMessage(locale, "AddressByDirect", _blockState._text);
378                    return Bundle.getMessage(locale, "Block_Long", namedBean, "", state);
379                } else {
380                    state = Bundle.getMessage(locale, "AddressByDirect", _blockState._text);
381                }
382               break;
383
384            case Reference:
385                state = Bundle.getMessage(locale, "AddressByReference", _stateReference);
386                break;
387
388            case LocalVariable:
389                state = Bundle.getMessage(locale, "AddressByLocalVariable", _stateLocalVariable);
390                break;
391
392            case Formula:
393                state = Bundle.getMessage(locale, "AddressByFormula", _stateFormula);
394                break;
395
396            default:
397                throw new IllegalArgumentException("invalid _stateAddressing state: " + _stateAddressing.name());
398        }
399
400
401        return Bundle.getMessage(locale, "Block_Long", namedBean, _is_IsNot.toString(), state);
402    }
403
404    /** {@inheritDoc} */
405    @Override
406    public void setup() {
407        // Do nothing
408    }
409
410    /** {@inheritDoc} */
411    @Override
412    public void registerListenersForThisClass() {
413        if (!_listenersAreRegistered) {
414            _selectNamedBean.addPropertyChangeListener(this);
415            _selectNamedBean.registerListeners();
416            _listenersAreRegistered = true;
417        }
418    }
419
420    /** {@inheritDoc} */
421    @Override
422    public void unregisterListenersForThisClass() {
423        if (_listenersAreRegistered) {
424            _selectNamedBean.removePropertyChangeListener(this);
425            _selectNamedBean.unregisterListeners();
426            _listenersAreRegistered = false;
427        }
428    }
429
430    /** {@inheritDoc} */
431    @Override
432    public void propertyChange(PropertyChangeEvent evt) {
433        getConditionalNG().execute();
434    }
435
436    /** {@inheritDoc} */
437    @Override
438    public void disposeMe() {
439    }
440
441    public enum BlockState {
442        Occupied(2, Bundle.getMessage("Block_StateOccupied")),
443        NotOccupied(4, Bundle.getMessage("Block_StateNotOccupied")),
444        Other(-1, Bundle.getMessage("Block_StateOther")),
445        Allocated(-2, Bundle.getMessage("Block_Allocated")),
446        ValueMatches(-3, Bundle.getMessage("Block_ValueMatches"));
447
448        private final int _id;
449        private final String _text;
450
451        private BlockState(int id, String text) {
452            this._id = id;
453            this._text = text;
454        }
455
456        public int getID() {
457            return _id;
458        }
459
460        @Override
461        public String toString() {
462            return _text;
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        _selectNamedBean.getUsageDetail(level, bean, report, cdl, this, LogixNG_SelectNamedBean.Type.Expression);
471    }
472
473    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionBlock.class);
474
475}