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        boolean left = false;
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            Object leftValue = _leftSide.calculate(symbolTable);
057            if (leftValue == null) leftValue = false;
058
059            if (!(leftValue instanceof Boolean)) {
060                if (TypeConversionUtil.isIntegerNumber(leftValue)) {
061                    // Convert to true or false
062                    leftValue = ((Number)leftValue).longValue() != 0;
063                } else {
064                    throw new CalculateException(Bundle.getMessage("ArithmeticNotBooleanOrIntegerNumberError", leftValue));
065                }
066            }
067
068            left = (Boolean)leftValue;
069
070            // The operators && and || only calculate the right value if
071            // the left value doesn't decide the result.
072            if ((_tokenType == TokenType.BOOLEAN_AND) && !left) {
073                return false;
074            }
075            if ((_tokenType == TokenType.BOOLEAN_OR) && left) {
076                return true;
077            }
078        }
079
080        Object rightValue = _rightSide.calculate(symbolTable);
081        if (rightValue == null) rightValue = false;
082
083        if (!(rightValue instanceof Boolean)) {
084            if (TypeConversionUtil.isIntegerNumber(rightValue)) {
085                // Convert to true or false
086                rightValue = ((Number)rightValue).longValue() != 0;
087            } else {
088                throw new CalculateException(Bundle.getMessage("ArithmeticNotBooleanOrIntegerNumberError", rightValue));
089            }
090        }
091        boolean right = (Boolean)rightValue;
092
093        if (_tokenType == TokenType.BOOLEAN_NOT) {
094            return ! right;
095        }
096
097        switch (_tokenType) {
098            case BOOLEAN_OR:
099                return left || right;
100
101            case BOOLEAN_XOR:
102                return (left && !right) || (!left && right);
103
104            case BOOLEAN_AND:
105                return left && right;
106
107            default:
108                throw new CalculateException("Unknown boolean operator: "+_tokenType.name());
109        }
110    }
111
112    /** {@inheritDoc} */
113    @Override
114    public String getDefinitionString() {
115        String operStr;
116        switch (_tokenType) {
117            case BOOLEAN_OR:
118                operStr = "||";
119                break;
120
121            case BOOLEAN_XOR:
122                operStr = "^^";
123                break;
124
125            case BOOLEAN_AND:
126                operStr = "&&";
127                break;
128
129            case BOOLEAN_NOT:
130                operStr = "!";
131                break;
132
133            default:
134                throw new UnsupportedOperationException("Unknown arithmetic operator: "+_tokenType.name());
135        }
136        if (_leftSide != null) {
137            return "("+_leftSide.getDefinitionString()+")" + operStr + "("+_rightSide.getDefinitionString()+")";
138        } else {
139            return operStr + "("+_rightSide.getDefinitionString()+")";
140        }
141    }
142
143}