Skip to content

Commit 717d045

Browse files
authored
Merge pull request #28 from sidhant92/arithmetic
Support for Arithmetic Expressions Evaluation
2 parents 467ac22 + 797980a commit 717d045

40 files changed

Lines changed: 1764 additions & 369 deletions
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.github.sidhant92.boolparser.application;
2+
3+
import java.util.Map;
4+
import java.util.Optional;
5+
import org.apache.commons.lang3.tuple.Pair;
6+
import com.github.sidhant92.boolparser.constant.ContainerDataType;
7+
import com.github.sidhant92.boolparser.constant.DataType;
8+
import com.github.sidhant92.boolparser.constant.Operator;
9+
import com.github.sidhant92.boolparser.domain.StringNode;
10+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticLeafNode;
11+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticNode;
12+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticUnaryNode;
13+
import com.github.sidhant92.boolparser.domain.Node;
14+
import com.github.sidhant92.boolparser.exception.UnsupportedToken;
15+
import com.github.sidhant92.boolparser.operator.OperatorService;
16+
import com.github.sidhant92.boolparser.parser.BoolExpressionParser;
17+
import com.github.sidhant92.boolparser.util.ValueUtils;
18+
import io.vavr.control.Try;
19+
import lombok.extern.slf4j.Slf4j;
20+
21+
/**
22+
* @author sidhant.aggarwal
23+
* @since 15/03/2024
24+
*/
25+
@Slf4j
26+
public class ArithmeticExpressionEvaluator {
27+
private final BoolExpressionParser boolExpressionParser;
28+
29+
private final OperatorService operatorService;
30+
31+
public ArithmeticExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
32+
this.boolExpressionParser = boolExpressionParser;
33+
operatorService = new OperatorService();
34+
}
35+
36+
public Try<Object> evaluate(final String expression, final Map<String, Object> data) {
37+
final Try<Node> tokenOptional = boolExpressionParser.parseExpression(expression, null);
38+
return tokenOptional.map(node -> evaluateToken(node, data));
39+
}
40+
41+
protected Object evaluate(final Node node, final Map<String, Object> data) {
42+
return evaluateToken(node, data);
43+
}
44+
45+
private Object evaluateToken(final Node node, final Map<String, Object> data) {
46+
switch (node.getTokenType()) {
47+
case ARITHMETIC:
48+
return evaluateArithmeticToken((ArithmeticNode) node, data);
49+
case ARITHMETIC_LEAF:
50+
return evaluateArithmeticLeafToken((ArithmeticLeafNode) node, data);
51+
case ARITHMETIC_UNARY:
52+
return evaluateUnaryArithmeticToken((ArithmeticUnaryNode) node, data);
53+
case STRING:
54+
return evaluateStringToken((StringNode) node, data);
55+
default:
56+
log.error("unsupported token {}", node.getTokenType());
57+
throw new UnsupportedToken();
58+
}
59+
}
60+
61+
private Object evaluateStringToken(final StringNode stringNode, final Map<String, Object> data) {
62+
return ValueUtils.getValueFromMap(stringNode.getField(), data).orElse(stringNode.getField());
63+
}
64+
65+
private Pair<Object, DataType> evaluateArithmeticLeafToken(final ArithmeticLeafNode arithmeticLeafNode, final Map<String, Object> data) {
66+
final Optional<Object> fetchedValue = ValueUtils.getValueFromMap(arithmeticLeafNode.getOperand().toString(), data);
67+
return fetchedValue
68+
.map(o -> Pair.of(o, ValueUtils.getDataType(o)))
69+
.orElseGet(() -> Pair.of(arithmeticLeafNode.getOperand(), arithmeticLeafNode.getDataType()));
70+
}
71+
72+
private Object evaluateUnaryArithmeticToken(final ArithmeticUnaryNode arithmeticUnaryNode, final Map<String, Object> data) {
73+
final Object resolvedValue = evaluateToken(arithmeticUnaryNode.getOperand(), data);
74+
if (resolvedValue instanceof Pair) {
75+
final Pair<Object, DataType> pair = (Pair<Object, DataType>) resolvedValue;
76+
return operatorService.evaluateArithmeticOperator(pair.getLeft(), pair.getRight(), null, null, Operator.UNARY,
77+
ContainerDataType.PRIMITIVE);
78+
}
79+
final DataType dataType = ValueUtils.getDataType(resolvedValue);
80+
return operatorService.evaluateArithmeticOperator(resolvedValue, dataType, null, null, Operator.UNARY, ContainerDataType.PRIMITIVE);
81+
}
82+
83+
private Object evaluateArithmeticToken(final ArithmeticNode arithmeticNode, final Map<String, Object> data) {
84+
final Object leftValue = evaluateToken(arithmeticNode.getLeft(), data);
85+
final Object rightValue = evaluateToken(arithmeticNode.getRight(), data);
86+
if (leftValue instanceof Pair && rightValue instanceof Pair) {
87+
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
88+
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
89+
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightPair.getLeft(), rightPair.getRight(),
90+
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
91+
} else if (leftValue instanceof Pair) {
92+
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
93+
final DataType rightDataType = ValueUtils.getDataType(rightValue);
94+
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightValue, rightDataType,
95+
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
96+
} else if (rightValue instanceof Pair) {
97+
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
98+
final DataType leftDataType = ValueUtils.getDataType(leftValue);
99+
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, rightPair.getLeft(), rightPair.getRight(),
100+
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
101+
} else {
102+
final DataType leftDataType = ValueUtils.getDataType(leftValue);
103+
final DataType rightDataType = ValueUtils.getDataType(rightValue);
104+
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, rightValue, rightDataType, arithmeticNode.getOperator(),
105+
ContainerDataType.PRIMITIVE);
106+
}
107+
}
108+
}

src/main/java/com/github/sidhant92/boolparser/application/BooleanExpressionEvaluator.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.github.sidhant92.boolparser.domain.ComparisonNode;
1313
import com.github.sidhant92.boolparser.domain.Node;
1414
import com.github.sidhant92.boolparser.domain.UnaryNode;
15+
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticBaseNode;
1516
import com.github.sidhant92.boolparser.exception.DataNotFoundException;
1617
import com.github.sidhant92.boolparser.exception.HeterogeneousArrayException;
1718
import com.github.sidhant92.boolparser.exception.InvalidUnaryOperand;
@@ -31,9 +32,12 @@ public class BooleanExpressionEvaluator {
3132

3233
private final OperatorService operatorService;
3334

35+
private final ArithmeticExpressionEvaluator arithmeticExpressionEvaluator;
36+
3437
public BooleanExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
3538
this.boolExpressionParser = boolExpressionParser;
3639
operatorService = new OperatorService();
40+
arithmeticExpressionEvaluator = new ArithmeticExpressionEvaluator(boolExpressionParser);
3741
}
3842

3943
public Try<Boolean> evaluate(final String expression, final Map<String, Object> data, final String defaultField) {
@@ -66,17 +70,17 @@ private boolean evaluateToken(final Node node, final Map<String, Object> data) {
6670

6771
private boolean evaluateComparisonToken(final ComparisonNode comparisonToken, final Map<String, Object> data) {
6872
final Object fieldData = ValueUtils.getValueFromMap(comparisonToken.getField(), data).orElseThrow(DataNotFoundException::new);
69-
return operatorService.evaluate(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(), fieldData,
70-
comparisonToken.getValue());
73+
final Object value = comparisonToken.getValue() instanceof ArithmeticBaseNode ? arithmeticExpressionEvaluator.evaluate(
74+
(Node) comparisonToken.getValue(), data) : comparisonToken.getValue();
75+
return operatorService.evaluateLogicalOperator(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(),
76+
fieldData, value);
7177
}
7278

7379
private boolean evaluateNumericRangeToken(final NumericRangeNode numericRangeToken, final Map<String, Object> data) {
7480
final Object fieldData = ValueUtils.getValueFromMap(numericRangeToken.getField(), data).orElseThrow(DataNotFoundException::new);
75-
return operatorService.evaluate(Operator.GREATER_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getFromDataType(), fieldData,
76-
numericRangeToken.getFromValue()) && operatorService.evaluate(Operator.LESS_THAN_EQUAL,
77-
ContainerDataType.PRIMITIVE,
78-
numericRangeToken.getToDataType(), fieldData,
79-
numericRangeToken.getToValue());
81+
return operatorService.evaluateLogicalOperator(Operator.GREATER_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getFromDataType(),
82+
fieldData, numericRangeToken.getFromValue()) && operatorService.evaluateLogicalOperator(
83+
Operator.LESS_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getToDataType(), fieldData, numericRangeToken.getToValue());
8084
}
8185

8286
private boolean evaluateInToken(final InNode inToken, final Map<String, Object> data) {
@@ -85,7 +89,7 @@ private boolean evaluateInToken(final InNode inToken, final Map<String, Object>
8589
final Object[] values = inToken.getItems()
8690
.stream()
8791
.map(Pair::getRight).toArray();
88-
return operatorService.evaluate(Operator.IN, ContainerDataType.PRIMITIVE, dataType, fieldData, values);
92+
return operatorService.evaluateLogicalOperator(Operator.IN, ContainerDataType.PRIMITIVE, dataType, fieldData, values);
8993
}
9094

9195
private boolean evaluateArrayToken(final ArrayNode arrayNode, final Map<String, Object> data) {
@@ -99,7 +103,7 @@ private boolean evaluateArrayToken(final ArrayNode arrayNode, final Map<String,
99103
final Object[] values = arrayNode.getItems()
100104
.stream()
101105
.map(Pair::getRight).toArray();
102-
return operatorService.evaluate(arrayNode.getOperator(), ContainerDataType.LIST, dataType, fieldData, values);
106+
return operatorService.evaluateLogicalOperator(arrayNode.getOperator(), ContainerDataType.LIST, dataType, fieldData, values);
103107
}
104108

105109
private boolean evaluateUnaryToken(final UnaryNode unaryToken, final Map<String, Object> data) {

src/main/java/com/github/sidhant92/boolparser/constant/NodeType.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ public enum NodeType {
1010
NUMERIC_RANGE,
1111
IN,
1212
ARRAY,
13-
UNARY
13+
UNARY,
14+
ARITHMETIC,
15+
ARITHMETIC_LEAF,
16+
ARITHMETIC_UNARY,
17+
STRING
1418
}

src/main/java/com/github/sidhant92/boolparser/constant/Operator.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.github.sidhant92.boolparser.constant;
22

33
import java.util.Optional;
4-
import com.github.sidhant92.boolparser.operator.AbstractOperator;
4+
import com.github.sidhant92.boolparser.operator.logical.AbstractOperator;
55
import com.github.sidhant92.boolparser.operator.OperatorFactory;
66
import lombok.AccessLevel;
77
import lombok.AllArgsConstructor;
@@ -21,15 +21,32 @@ public enum Operator {
2121
LESS_THAN_EQUAL,
2222
NOT_EQUAL,
2323
IN,
24+
25+
ADD,
26+
SUBTRACT,
27+
MULTIPLY,
28+
DIVIDE,
29+
MODULUS,
30+
EXPONENT,
31+
UNARY,
32+
2433
CONTAINS_ALL,
2534
CONTAINS_ANY;
2635

2736
public static Optional<Operator> getOperatorFromSymbol(final String symbol) {
2837
final String symbolLowerCase = symbol.toLowerCase();
29-
return OperatorFactory.getAllOperators()
38+
final Optional<Operator> operator = OperatorFactory.getAllLogicalOperators()
3039
.stream()
31-
.filter(operator -> operator.getSymbol().toLowerCase().equals(symbolLowerCase))
40+
.filter(op -> op.getSymbol().toLowerCase().equals(symbolLowerCase))
3241
.map(AbstractOperator::getOperator)
3342
.findFirst();
43+
if (operator.isPresent()) {
44+
return operator;
45+
}
46+
return OperatorFactory.getAllArithmeticOperators()
47+
.stream()
48+
.filter(op -> op.getSymbol().toLowerCase().equals(symbolLowerCase))
49+
.map(com.github.sidhant92.boolparser.operator.arithmetic.AbstractOperator::getOperator)
50+
.findFirst();
3451
}
3552
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.sidhant92.boolparser.domain;
2+
3+
import com.github.sidhant92.boolparser.constant.NodeType;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.Setter;
8+
9+
/**
10+
* @author sidhant.aggarwal
11+
* @since 16/03/2024
12+
*/
13+
@AllArgsConstructor
14+
@Getter
15+
@Setter
16+
@Builder
17+
public class StringNode extends Node {
18+
private final String field;
19+
20+
@Override
21+
public NodeType getTokenType() {
22+
return NodeType.STRING;
23+
}
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.github.sidhant92.boolparser.domain.arithmetic;
2+
3+
import com.github.sidhant92.boolparser.domain.Node;
4+
5+
/**
6+
* @author sidhant.aggarwal
7+
* @since 16/03/2024
8+
*/
9+
public abstract class ArithmeticBaseNode extends Node {}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.sidhant92.boolparser.domain.arithmetic;
2+
3+
import com.github.sidhant92.boolparser.constant.DataType;
4+
import com.github.sidhant92.boolparser.constant.NodeType;
5+
import com.github.sidhant92.boolparser.domain.Node;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.Setter;
10+
11+
/**
12+
* @author sidhant.aggarwal
13+
* @since 15/03/2024
14+
*/
15+
@AllArgsConstructor
16+
@Getter
17+
@Setter
18+
@Builder
19+
public class ArithmeticLeafNode extends ArithmeticBaseNode {
20+
private Object operand;
21+
22+
private DataType dataType;
23+
24+
@Override
25+
public NodeType getTokenType() {
26+
return NodeType.ARITHMETIC_LEAF;
27+
}
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.github.sidhant92.boolparser.domain.arithmetic;
2+
3+
import com.github.sidhant92.boolparser.constant.NodeType;
4+
import com.github.sidhant92.boolparser.constant.Operator;
5+
import com.github.sidhant92.boolparser.domain.Node;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.Setter;
10+
11+
/**
12+
* @author sidhant.aggarwal
13+
* @since 15/03/2024
14+
*/
15+
@AllArgsConstructor
16+
@Getter
17+
@Setter
18+
@Builder
19+
public class ArithmeticNode extends ArithmeticBaseNode {
20+
private Node left;
21+
22+
private Node right;
23+
24+
private final Operator operator;
25+
26+
@Override
27+
public NodeType getTokenType() {
28+
return NodeType.ARITHMETIC;
29+
}
30+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.sidhant92.boolparser.domain.arithmetic;
2+
3+
import com.github.sidhant92.boolparser.constant.NodeType;
4+
import com.github.sidhant92.boolparser.domain.Node;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import lombok.Setter;
9+
10+
/**
11+
* @author sidhant.aggarwal
12+
* @since 15/03/2024
13+
*/
14+
@AllArgsConstructor
15+
@Getter
16+
@Setter
17+
@Builder
18+
public class ArithmeticUnaryNode extends ArithmeticBaseNode {
19+
private Node operand;
20+
21+
@Override
22+
public NodeType getTokenType() {
23+
return NodeType.ARITHMETIC_UNARY;
24+
}
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.sidhant92.boolparser.exception;
2+
3+
public class UnsupportedToken extends RuntimeException {
4+
public UnsupportedToken(final String message) {
5+
super(message);
6+
}
7+
8+
public UnsupportedToken() {
9+
super();
10+
}
11+
12+
@Override
13+
public String getMessage() {
14+
return "Unsupported Token";
15+
}
16+
}

0 commit comments

Comments
 (0)