001package jmri.jmrit.logixng.util.parser;
002
003import jmri.JmriException;
004import jmri.jmrit.logixng.SymbolTable;
005import jmri.util.TypeConversionUtil;
006
007/**
008 * A parsed expression
009 */
010public class ExpressionNodeBooleanOperator implements ExpressionNode {
011
012    private final TokenType _tokenType;
013    private final ExpressionNode _leftSide;
014    private final ExpressionNode _rightSide;
015
016    public ExpressionNodeBooleanOperator(TokenType tokenType, ExpressionNode leftSide, ExpressionNode rightSide) {
017        _tokenType = tokenType;
018        _leftSide = leftSide;
019        _rightSide = rightSide;
020
021        if (_rightSide == null) {
022            throw new IllegalArgumentException("rightSide must not be null");
023        }
024
025        // Verify that the token is of the correct type
026        switch (_tokenType) {
027            case BOOLEAN_OR:
028            case BOOLEAN_XOR:
029            case BOOLEAN_AND:
030                if (_leftSide == null) {
031                    throw new IllegalArgumentException("leftSide must not be null for operators AND, OR and XOR");
032                }
033                break;
034
035            case BOOLEAN_NOT:
036                if (_leftSide != null) {
037                    throw new IllegalArgumentException("leftSide must be null for operator NOT");
038                }
039                break;
040
041            default:
042                throw new IllegalArgumentException("Unsupported boolean operator: "+_tokenType.name());
043        }
044    }
045
046    @Override
047    public Object calculate(SymbolTable symbolTable) throws JmriException {
048
049        Object leftValue = null;
050        if (_tokenType != TokenType.BOOLEAN_NOT) {
051            // Left value must be calculated _before_ right value is calculated.
052            // When a value is calculated, a method might be called, and the
053            // order of these calls must be correct.
054            // For example, if myArray is an array, the formula might be:
055            //   myArray.add("Hello") || myArray.add(" ") || myArray.add("World!")
056            leftValue = _leftSide.calculate(symbolTable);
057        }
058        if (leftValue == null) leftValue = false;
059
060        Object rightValue = _rightSide.calculate(symbolTable);
061        if (rightValue == null) rightValue = false;
062
063        if (!(rightValue instanceof Boolean)) {
064            if (TypeConversionUtil.isIntegerNumber(rightValue)) {
065                // Convert to true or false
066                rightValue = ((Number)rightValue).longValue() != 0;
067            } else {
068                throw new CalculateException(Bundle.getMessage("ArithmeticNotBooleanOrIntegerNumberError", rightValue));
069            }
070        }
071        boolean right = (Boolean)rightValue;
072
073        if (_tokenType == TokenType.BOOLEAN_NOT) {
074            return ! right;
075        }
076
077        if (!(leftValue instanceof Boolean)) {
078            if (TypeConversionUtil.isIntegerNumber(leftValue)) {
079                // Convert to true or false
080                leftValue = ((Number)leftValue).longValue() != 0;
081            } else {
082                throw new CalculateException(Bundle.getMessage("ArithmeticNotBooleanOrIntegerNumberError", leftValue));
083            }
084        }
085        boolean left = (Boolean)leftValue;
086
087        switch (_tokenType) {
088            case BOOLEAN_OR:
089                return left || right;
090
091            case BOOLEAN_XOR:
092                return (left && !right) || (!left && right);
093
094            case BOOLEAN_AND:
095                return left && right;
096
097            default:
098                throw new CalculateException("Unknown boolean operator: "+_tokenType.name());
099        }
100    }
101
102    /** {@inheritDoc} */
103    @Override
104    public String getDefinitionString() {
105        String operStr;
106        switch (_tokenType) {
107            case BOOLEAN_OR:
108                operStr = "||";
109                break;
110
111            case BOOLEAN_XOR:
112                operStr = "^^";
113                break;
114
115            case BOOLEAN_AND:
116                operStr = "&&";
117                break;
118
119            case BOOLEAN_NOT:
120                operStr = "!";
121                break;
122
123            default:
124                throw new UnsupportedOperationException("Unknown arithmetic operator: "+_tokenType.name());
125        }
126        if (_leftSide != null) {
127            return "("+_leftSide.getDefinitionString()+")" + operStr + "("+_rightSide.getDefinitionString()+")";
128        } else {
129            return operStr + "("+_rightSide.getDefinitionString()+")";
130        }
131    }
132
133}