Skip to content

Commit 8dffdb0

Browse files
committed
support for arithmetic expressions
1 parent 896d77b commit 8dffdb0

37 files changed

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

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,15 @@ private boolean evaluateToken(final Node node, final Map<String, Object> data) {
6666

6767
private boolean evaluateComparisonToken(final ComparisonNode comparisonToken, final Map<String, Object> data) {
6868
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());
69+
return operatorService.evaluateLogicalOperator(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(),
70+
fieldData, comparisonToken.getValue());
7171
}
7272

7373
private boolean evaluateNumericRangeToken(final NumericRangeNode numericRangeToken, final Map<String, Object> data) {
7474
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());
75+
return operatorService.evaluateLogicalOperator(Operator.GREATER_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getFromDataType(),
76+
fieldData, numericRangeToken.getFromValue()) && operatorService.evaluateLogicalOperator(
77+
Operator.LESS_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getToDataType(), fieldData, numericRangeToken.getToValue());
8078
}
8179

8280
private boolean evaluateInToken(final InNode inToken, final Map<String, Object> data) {
@@ -85,7 +83,7 @@ private boolean evaluateInToken(final InNode inToken, final Map<String, Object>
8583
final Object[] values = inToken.getItems()
8684
.stream()
8785
.map(Pair::getRight).toArray();
88-
return operatorService.evaluate(Operator.IN, ContainerDataType.PRIMITIVE, dataType, fieldData, values);
86+
return operatorService.evaluateLogicalOperator(Operator.IN, ContainerDataType.PRIMITIVE, dataType, fieldData, values);
8987
}
9088

9189
private boolean evaluateArrayToken(final ArrayNode arrayNode, final Map<String, Object> data) {
@@ -99,7 +97,7 @@ private boolean evaluateArrayToken(final ArrayNode arrayNode, final Map<String,
9997
final Object[] values = arrayNode.getItems()
10098
.stream()
10199
.map(Pair::getRight).toArray();
102-
return operatorService.evaluate(arrayNode.getOperator(), ContainerDataType.LIST, dataType, fieldData, values);
100+
return operatorService.evaluateLogicalOperator(arrayNode.getOperator(), ContainerDataType.LIST, dataType, fieldData, values);
103101
}
104102

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

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

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

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: 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 Node {
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 Node {
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 Node {
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+
}

src/main/java/com/github/sidhant92/boolparser/operator/OperatorFactory.java

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,33 @@
55
import java.util.List;
66
import java.util.Map;
77
import com.github.sidhant92.boolparser.constant.Operator;
8+
import com.github.sidhant92.boolparser.operator.arithmetic.AddOperator;
9+
import com.github.sidhant92.boolparser.operator.arithmetic.DivideOperator;
10+
import com.github.sidhant92.boolparser.operator.arithmetic.ExponentOperator;
11+
import com.github.sidhant92.boolparser.operator.arithmetic.ModulusOperator;
12+
import com.github.sidhant92.boolparser.operator.arithmetic.MultiplyOperator;
13+
import com.github.sidhant92.boolparser.operator.arithmetic.SubtractOperator;
14+
import com.github.sidhant92.boolparser.operator.arithmetic.UnaryOperator;
15+
import com.github.sidhant92.boolparser.operator.logical.AbstractOperator;
16+
import com.github.sidhant92.boolparser.operator.logical.ContainsAllOperator;
17+
import com.github.sidhant92.boolparser.operator.logical.ContainsAnyOperator;
18+
import com.github.sidhant92.boolparser.operator.logical.EqualsOperator;
19+
import com.github.sidhant92.boolparser.operator.logical.GreaterThanEqualOperator;
20+
import com.github.sidhant92.boolparser.operator.logical.GreaterThanOperator;
21+
import com.github.sidhant92.boolparser.operator.logical.InOperator;
22+
import com.github.sidhant92.boolparser.operator.logical.LessThanEqualOperator;
23+
import com.github.sidhant92.boolparser.operator.logical.LessThanOperator;
24+
import com.github.sidhant92.boolparser.operator.logical.NotEqualsOperator;
825

926
/**
1027
* @author sidhant.aggarwal
1128
* @since 05/03/2023
1229
*/
1330
public class OperatorFactory {
14-
private static final Map<Operator, AbstractOperator> operatorMap = new EnumMap<>(Operator.class);
31+
private static final Map<Operator, AbstractOperator> logicalOperatorMap = new EnumMap<>(Operator.class);
32+
33+
private static final Map<Operator, com.github.sidhant92.boolparser.operator.arithmetic.AbstractOperator> arithmeticOperatorMap = new EnumMap<>(
34+
Operator.class);
1535

1636
private OperatorFactory() {
1737
super();
@@ -20,26 +40,38 @@ private OperatorFactory() {
2040
public static void initialize() {
2141
final EqualsOperator equalsOperator = new EqualsOperator();
2242
final InOperator inOperator = new InOperator(equalsOperator);
23-
operatorMap.put(Operator.EQUALS, equalsOperator);
24-
operatorMap.put(Operator.GREATER_THAN, new GreaterThanOperator());
25-
operatorMap.put(Operator.GREATER_THAN_EQUAL, new GreaterThanEqualOperator());
26-
operatorMap.put(Operator.LESS_THAN, new LessThanOperator());
27-
operatorMap.put(Operator.LESS_THAN_EQUAL, new LessThanEqualOperator());
28-
operatorMap.put(Operator.NOT_EQUAL, new NotEqualsOperator());
29-
operatorMap.put(Operator.IN, new InOperator(equalsOperator));
30-
operatorMap.put(Operator.CONTAINS_ALL, new ContainsAllOperator(inOperator));
31-
operatorMap.put(Operator.CONTAINS_ANY, new ContainsAnyOperator(inOperator));
43+
logicalOperatorMap.put(Operator.EQUALS, equalsOperator);
44+
logicalOperatorMap.put(Operator.GREATER_THAN, new GreaterThanOperator());
45+
logicalOperatorMap.put(Operator.GREATER_THAN_EQUAL, new GreaterThanEqualOperator());
46+
logicalOperatorMap.put(Operator.LESS_THAN, new LessThanOperator());
47+
logicalOperatorMap.put(Operator.LESS_THAN_EQUAL, new LessThanEqualOperator());
48+
logicalOperatorMap.put(Operator.NOT_EQUAL, new NotEqualsOperator());
49+
logicalOperatorMap.put(Operator.IN, new InOperator(equalsOperator));
50+
logicalOperatorMap.put(Operator.CONTAINS_ALL, new ContainsAllOperator(inOperator));
51+
logicalOperatorMap.put(Operator.CONTAINS_ANY, new ContainsAnyOperator(inOperator));
52+
53+
arithmeticOperatorMap.put(Operator.ADD, new AddOperator());
54+
arithmeticOperatorMap.put(Operator.SUBTRACT, new SubtractOperator());
55+
arithmeticOperatorMap.put(Operator.DIVIDE, new DivideOperator());
56+
arithmeticOperatorMap.put(Operator.MULTIPLY, new MultiplyOperator());
57+
arithmeticOperatorMap.put(Operator.EXPONENT, new ExponentOperator());
58+
arithmeticOperatorMap.put(Operator.MODULUS, new ModulusOperator());
59+
arithmeticOperatorMap.put(Operator.UNARY, new UnaryOperator());
60+
}
61+
62+
public static AbstractOperator getLogicalOperator(final Operator operator) {
63+
return logicalOperatorMap.get(operator);
3264
}
3365

34-
public static AbstractOperator getOperator(final Operator operator) {
35-
return operatorMap.get(operator);
66+
public static com.github.sidhant92.boolparser.operator.arithmetic.AbstractOperator getArithmeticOperator(final Operator operator) {
67+
return arithmeticOperatorMap.get(operator);
3668
}
3769

38-
public static List<AbstractOperator> getAllOperators() {
39-
return new ArrayList<>(operatorMap.values());
70+
public static List<AbstractOperator> getAllLogicalOperators() {
71+
return new ArrayList<>(logicalOperatorMap.values());
4072
}
4173

42-
public static void register(final AbstractOperator abstractOperator) {
43-
operatorMap.put(abstractOperator.getOperator(), abstractOperator);
74+
public static List<com.github.sidhant92.boolparser.operator.arithmetic.AbstractOperator> getAllArithmeticOperators() {
75+
return new ArrayList<>(arithmeticOperatorMap.values());
4476
}
4577
}

0 commit comments

Comments
 (0)