Skip to content

Commit 4dce460

Browse files
authored
Merge pull request #36 from sidhant92/array_math_functions
Code Cleanup
2 parents db3a80a + 6f1a8e4 commit 4dce460

42 files changed

Lines changed: 1020 additions & 941 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ A Boolean Expression Parser for Java
44
The library can help parse complex and nested boolean expressions.
55
The expressions are in SQL-like syntax, where you can use boolean operators and parentheses to combine individual expressions.
66

7-
An expression can be as simple as `name = Sidhant`.
7+
An expression can be as simple as `name = 'Sidhant'`.
88
A Complex expression is formed by combining these small expressions by logical operators and giving precedence using parenthesis
99

1010
### Examples
1111
#### Textual Equality
1212

1313
Format: `${attributeName} = ${value}`
1414

15-
Example: `name = john`
15+
Example: `name = 'john'`
1616

1717
#### Numeric Comparisons
1818

@@ -34,7 +34,7 @@ Example: `price 5.99 TO 100`
3434

3535
Example:
3636

37-
`price < 10 AND (category:Book OR NOT category:Ebook)`
37+
`price < 10 AND (category:'Book' OR NOT category:'Ebook')`
3838

3939
Individual filters can be combined via boolean operators. The following operators are supported:
4040

@@ -45,6 +45,8 @@ Individual filters can be combined via boolean operators. The following operator
4545
Parentheses, `(` and `)`, can be used for grouping.
4646

4747
#### Usage Notes
48+
* String must be enclosed either in single or double quotes.
49+
* Variables substitution is supported by passing the name of the variable without the quotes.
4850
* Phrases that includes quotes, like `content = "It's a wonderful day"`
4951
* Phrases that includes quotes, like `attribute = 'She said "Hello World"'`
5052
* For nested keys in data map you can use the dot notation, like `person.age`
@@ -72,7 +74,7 @@ dependencies {
7274
Code
7375
```
7476
final BoolParser boolParser = new BoolParser();
75-
final Try<Node> nodeOptional = boolParser.parseExpression("name = test");
77+
final Try<Node> nodeOptional = boolParser.parseExpression("name = 'test'");
7678
```
7779

7880
### Node Types Post Parsing
@@ -120,12 +122,18 @@ private final DataType dataType;
120122
private final Object value;
121123
```
122124

125+
####
126+
FieldNode
127+
```
128+
private final String field;
129+
```
130+
123131
####
124132
InNode
125133
```
126134
private final String field;
127135
128-
private final List<Pair<DataType, Object>> items;
136+
private final List<Node> items;
129137
```
130138

131139

@@ -160,7 +168,7 @@ final BooleanExpressionEvaluator booleanExpressionEvaluator = new BooleanExpress
160168
final Map<String, Object> data = new HashMap<>();
161169
data.put("age", 25);
162170
data.put("name", "sid");
163-
final Try<Boolean> resultOptional = booleanExpressionEvaluator.evaluate("name = sid AND age = 25", data);
171+
final Try<Boolean> resultOptional = booleanExpressionEvaluator.evaluate("name = 'sid' AND age = 25", data);
164172
assertTrue(resultOptional.isPresent());
165173
assertTrue(resultOptional.get());
166174
```
@@ -171,7 +179,7 @@ final Map<String, Object> data = new HashMap<>();
171179
data.put("age", 25);
172180
data.put("name", "sid");
173181
data.put("num", 45);
174-
final Try<Boolean> resultOptional = booleanExpressionEvaluator.evaluate("name:sid AND (age = 25 OR num = 44)", data);
182+
final Try<Boolean> resultOptional = booleanExpressionEvaluator.evaluate("name = sid AND (age = 25 OR num = 44)", data);
175183
assertTrue(resultOptional.isPresent());
176184
assertTrue(resultOptional.get());
177185
```
@@ -211,6 +219,22 @@ The following Operators are supported:
211219
5. Modulus (%)
212220
6. Exponent (^)
213221

222+
The following functions are supported:
223+
1. Minimum (min)
224+
2. Maximum (max)
225+
3. Average (avg)
226+
4. Sum (sum)
227+
5. Mean (mean)
228+
6. Mode (mode)
229+
7. Median (median)
230+
8. Integer (int) - converts the input to integer
231+
9. Length (len) - Returns length of the give array
232+
233+
Syntax For using functions
234+
Format: `${FunctionIdentifier} (item1, item2...)`
235+
236+
Example: `min (1,2,3)` or with variable substitution `min (a,b,c)`
237+
214238
Usage examples:
215239

216240
Simple Addition Operation
@@ -232,5 +256,14 @@ final Try<Object> resultOptional = evaluator.evaluate("((5 * 2) + a) * 2 + (1 +
232256
assertTrue(resultOptional.isPresent());
233257
assertTrue(resultOptional.get(), 56);
234258
```
259+
Function Usage
260+
```
261+
final ArithmeticExpressionEvaluator evaluator = new ArithmeticExpressionEvaluator(new Boolparser());
262+
final Map<String, Object> data = new HashMap<>();
263+
data.put("a", 10);
264+
final Try<Object> resultOptional = arithmeticExpressionEvaluator.evaluate("min (1,2,3)", data);
265+
assertTrue(resultOptional.isSuccess());
266+
assertEquals(resultOptional.get(), 1);
267+
```
235268

236269
[For a complete list of examples please check out the test file](src/test/java/com/github/sidhant92/boolparser/application/ArithmeticExpressionEvaluatorTest.java)

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

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
package com.github.sidhant92.boolparser.application;
22

3-
import java.util.ArrayList;
4-
import java.util.Collection;
53
import java.util.List;
64
import java.util.Map;
7-
import java.util.Optional;
85
import java.util.stream.Collectors;
9-
import org.apache.commons.lang3.tuple.Pair;
106
import com.github.sidhant92.boolparser.constant.ContainerDataType;
117
import com.github.sidhant92.boolparser.constant.DataType;
128
import com.github.sidhant92.boolparser.constant.Operator;
13-
import com.github.sidhant92.boolparser.domain.StringNode;
14-
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticLeafNode;
9+
import com.github.sidhant92.boolparser.domain.EvaluatedNode;
10+
import com.github.sidhant92.boolparser.domain.FieldNode;
11+
import com.github.sidhant92.boolparser.domain.arithmetic.UnaryNode;
1512
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticNode;
16-
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticUnaryNode;
17-
import com.github.sidhant92.boolparser.domain.Node;
13+
import com.github.sidhant92.boolparser.domain.logical.Node;
1814
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticFunctionNode;
15+
import com.github.sidhant92.boolparser.exception.DataNotFoundException;
1916
import com.github.sidhant92.boolparser.exception.UnsupportedToken;
2017
import com.github.sidhant92.boolparser.function.FunctionEvaluatorService;
2118
import com.github.sidhant92.boolparser.operator.OperatorService;
@@ -55,76 +52,66 @@ private Object evaluateToken(final Node node, final Map<String, Object> data) {
5552
switch (node.getTokenType()) {
5653
case ARITHMETIC:
5754
return evaluateArithmeticToken((ArithmeticNode) node, data);
58-
case ARITHMETIC_LEAF:
59-
return evaluateArithmeticLeafToken((ArithmeticLeafNode) node, data);
60-
case ARITHMETIC_UNARY:
61-
return evaluateUnaryArithmeticToken((ArithmeticUnaryNode) node, data);
6255
case ARITHMETIC_FUNCTION:
6356
return evaluateArithmeticFunctionToken((ArithmeticFunctionNode) node, data);
64-
case STRING:
65-
return evaluateStringToken((StringNode) node, data);
57+
case UNARY:
58+
return evaluateUnaryToken((UnaryNode) node, data);
59+
case FIELD:
60+
return evaluateFieldToken((FieldNode) node, data);
6661
default:
6762
log.error("unsupported token {}", node.getTokenType());
6863
throw new UnsupportedToken();
6964
}
7065
}
7166

72-
private Object evaluateStringToken(final StringNode stringNode, final Map<String, Object> data) {
73-
return ValueUtils.getValueFromMap(stringNode.getField(), data).orElse(stringNode.getField());
74-
}
75-
76-
private Pair<Object, DataType> evaluateArithmeticLeafToken(final ArithmeticLeafNode arithmeticLeafNode, final Map<String, Object> data) {
77-
final Optional<Object> fetchedValue = arithmeticLeafNode.getDataType() != DataType.STRING ? Optional.of(
78-
arithmeticLeafNode.getOperand()) : ValueUtils.getValueFromMap(arithmeticLeafNode.getOperand().toString(), data);
79-
return fetchedValue
80-
.map(o -> Pair.of(o, ValueUtils.getDataType(o)))
81-
.orElseGet(() -> Pair.of(arithmeticLeafNode.getOperand(), arithmeticLeafNode.getDataType()));
67+
private Object evaluateFieldToken(final FieldNode fieldNode, final Map<String, Object> data) {
68+
if (!data.containsKey(fieldNode.getField())) {
69+
throw new DataNotFoundException();
70+
}
71+
return data.get(fieldNode.getField());
8272
}
8373

84-
private Object evaluateUnaryArithmeticToken(final ArithmeticUnaryNode arithmeticUnaryNode, final Map<String, Object> data) {
85-
final Object resolvedValue = evaluateToken(arithmeticUnaryNode.getOperand(), data);
86-
if (resolvedValue instanceof Pair) {
87-
final Pair<Object, DataType> pair = (Pair<Object, DataType>) resolvedValue;
88-
return operatorService.evaluateArithmeticOperator(pair.getLeft(), pair.getRight(), null, null, Operator.UNARY,
89-
ContainerDataType.PRIMITIVE);
90-
}
91-
final DataType dataType = ValueUtils.getDataType(resolvedValue);
92-
return operatorService.evaluateArithmeticOperator(resolvedValue, dataType, null, null, Operator.UNARY, ContainerDataType.PRIMITIVE);
74+
private Object evaluateUnaryToken(final UnaryNode unaryNode, final Map<String, Object> data) {
75+
return unaryNode.getValue();
9376
}
9477

9578
private Object evaluateArithmeticFunctionToken(final ArithmeticFunctionNode arithmeticFunctionNode, final Map<String, Object> data) {
96-
final List<Pair<Object, DataType>> resolvedValues = arithmeticFunctionNode.getItems()
79+
final List<Object> resolvedValues = arithmeticFunctionNode.getItems()
9780
.stream()
98-
.map(item -> evaluateArithmeticLeafToken(item, data))
81+
.map(item -> evaluate(item, data))
9982
.collect(Collectors.toList());
100-
final List<Pair<Object, DataType>> flattenedValues = new ArrayList<>();
101-
resolvedValues.forEach(value -> {
102-
if (value.getKey() instanceof Collection) {
103-
((Collection<?>) value.getKey()).forEach(v -> flattenedValues.add(Pair.of(v, ValueUtils.getDataType(v))));
104-
} else {
105-
flattenedValues.add(value);
106-
}
107-
});
83+
final List<EvaluatedNode> flattenedValues = ValueUtils.mapToEvaluatedNodes(resolvedValues);
10884
return functionEvaluatorService.evaluateArithmeticFunction(arithmeticFunctionNode.getFunctionType(), flattenedValues);
10985
}
11086

11187
private Object evaluateArithmeticToken(final ArithmeticNode arithmeticNode, final Map<String, Object> data) {
11288
final Object leftValue = evaluateToken(arithmeticNode.getLeft(), data);
89+
if (arithmeticNode.getOperator().equals(Operator.UNARY)) {
90+
if (leftValue instanceof EvaluatedNode) {
91+
final EvaluatedNode left = (EvaluatedNode) leftValue;
92+
return operatorService.evaluateArithmeticOperator(left.getValue(), left.getDataType(), null, null, arithmeticNode.getOperator(),
93+
ContainerDataType.PRIMITIVE);
94+
} else {
95+
final DataType leftDataType = ValueUtils.getDataType(leftValue);
96+
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, null, null, arithmeticNode.getOperator(),
97+
ContainerDataType.PRIMITIVE);
98+
}
99+
}
113100
final Object rightValue = evaluateToken(arithmeticNode.getRight(), data);
114-
if (leftValue instanceof Pair && rightValue instanceof Pair) {
115-
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
116-
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
117-
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightPair.getLeft(), rightPair.getRight(),
101+
if (leftValue instanceof EvaluatedNode && rightValue instanceof EvaluatedNode) {
102+
final EvaluatedNode left = (EvaluatedNode) leftValue;
103+
final EvaluatedNode right = (EvaluatedNode) rightValue;
104+
return operatorService.evaluateArithmeticOperator(left.getValue(), left.getDataType(), right.getValue(), right.getDataType(),
118105
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
119-
} else if (leftValue instanceof Pair) {
120-
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
106+
} else if (leftValue instanceof EvaluatedNode) {
107+
final EvaluatedNode left = (EvaluatedNode) leftValue;
121108
final DataType rightDataType = ValueUtils.getDataType(rightValue);
122-
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightValue, rightDataType,
109+
return operatorService.evaluateArithmeticOperator(left.getValue(), left.getDataType(), rightValue, rightDataType,
123110
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
124-
} else if (rightValue instanceof Pair) {
125-
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
111+
} else if (rightValue instanceof EvaluatedNode) {
112+
final EvaluatedNode right = (EvaluatedNode) rightValue;
126113
final DataType leftDataType = ValueUtils.getDataType(leftValue);
127-
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, rightPair.getLeft(), rightPair.getRight(),
114+
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, right.getValue(), right.getDataType(),
128115
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
129116
} else {
130117
final DataType leftDataType = ValueUtils.getDataType(leftValue);

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

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package com.github.sidhant92.boolparser.application;
22

3+
import java.util.List;
34
import java.util.Map;
4-
import org.apache.commons.lang3.tuple.Pair;
5+
import java.util.stream.Collectors;
56
import com.github.sidhant92.boolparser.constant.ContainerDataType;
67
import com.github.sidhant92.boolparser.constant.DataType;
78
import com.github.sidhant92.boolparser.constant.Operator;
8-
import com.github.sidhant92.boolparser.domain.ArrayNode;
9-
import com.github.sidhant92.boolparser.domain.BooleanNode;
10-
import com.github.sidhant92.boolparser.domain.InNode;
11-
import com.github.sidhant92.boolparser.domain.NumericRangeNode;
12-
import com.github.sidhant92.boolparser.domain.ComparisonNode;
13-
import com.github.sidhant92.boolparser.domain.Node;
14-
import com.github.sidhant92.boolparser.domain.UnaryNode;
9+
import com.github.sidhant92.boolparser.domain.logical.ArrayNode;
10+
import com.github.sidhant92.boolparser.domain.logical.BooleanNode;
11+
import com.github.sidhant92.boolparser.domain.EvaluatedNode;
12+
import com.github.sidhant92.boolparser.domain.logical.InNode;
13+
import com.github.sidhant92.boolparser.domain.logical.NumericRangeNode;
14+
import com.github.sidhant92.boolparser.domain.logical.ComparisonNode;
15+
import com.github.sidhant92.boolparser.domain.logical.Node;
16+
import com.github.sidhant92.boolparser.domain.arithmetic.UnaryNode;
1517
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticBaseNode;
1618
import com.github.sidhant92.boolparser.exception.DataNotFoundException;
1719
import com.github.sidhant92.boolparser.exception.HeterogeneousArrayException;
@@ -71,7 +73,7 @@ private boolean evaluateToken(final Node node, final Map<String, Object> data) {
7173
private boolean evaluateComparisonToken(final ComparisonNode comparisonToken, final Map<String, Object> data) {
7274
final Object fieldData = ValueUtils.getValueFromMap(comparisonToken.getField(), data).orElseThrow(DataNotFoundException::new);
7375
final Object value = comparisonToken.getValue() instanceof ArithmeticBaseNode ? arithmeticExpressionEvaluator.evaluate(
74-
(Node) comparisonToken.getValue(), data) : comparisonToken.getValue();
76+
comparisonToken.getValue(), data) : comparisonToken.getValue();
7577
return operatorService.evaluateLogicalOperator(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(),
7678
fieldData, value);
7779
}
@@ -85,24 +87,39 @@ private boolean evaluateNumericRangeToken(final NumericRangeNode numericRangeTok
8587

8688
private boolean evaluateInToken(final InNode inToken, final Map<String, Object> data) {
8789
final Object fieldData = ValueUtils.getValueFromMap(inToken.getField(), data).orElseThrow(DataNotFoundException::new);
90+
final List<EvaluatedNode> items = resolveArrayElements(inToken.getItems(), data);
8891
final DataType dataType = ValueUtils.getDataType(fieldData);
89-
final Object[] values = inToken.getItems()
92+
final Object[] values = items
9093
.stream()
91-
.map(Pair::getRight).toArray();
94+
.map(EvaluatedNode::getValue).toArray();
9295
return operatorService.evaluateLogicalOperator(Operator.IN, ContainerDataType.PRIMITIVE, dataType, fieldData, values);
9396
}
9497

98+
private List<EvaluatedNode> resolveArrayElements(final List<Node> items, final Map<String, Object> data) {
99+
final List<Object> resolvedValues = items
100+
.stream()
101+
.map(item -> {
102+
if (item instanceof ArithmeticBaseNode) {
103+
return arithmeticExpressionEvaluator.evaluate(item, data);
104+
}
105+
return evaluateToken(item, data);
106+
})
107+
.collect(Collectors.toList());
108+
return ValueUtils.mapToEvaluatedNodes(resolvedValues);
109+
}
110+
95111
private boolean evaluateArrayToken(final ArrayNode arrayNode, final Map<String, Object> data) {
96112
final Object fieldData = ValueUtils.getValueFromMap(arrayNode.getField(), data).orElseThrow(DataNotFoundException::new);
97-
if (arrayNode.getItems()
113+
final List<EvaluatedNode> items = resolveArrayElements(arrayNode.getItems(), data);
114+
if (items
98115
.stream()
99-
.map(Pair::getLeft).distinct().count() > 1) {
116+
.map(EvaluatedNode::getDataType).distinct().count() > 1) {
100117
throw new HeterogeneousArrayException();
101118
}
102-
final DataType dataType = arrayNode.getItems().get(0).getLeft();
103-
final Object[] values = arrayNode.getItems()
119+
final DataType dataType = items.get(0).getDataType();
120+
final Object[] values = items
104121
.stream()
105-
.map(Pair::getRight).toArray();
122+
.map(EvaluatedNode::getValue).toArray();
106123
return operatorService.evaluateLogicalOperator(arrayNode.getOperator(), ContainerDataType.LIST, dataType, fieldData, values);
107124
}
108125

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ public enum NodeType {
1111
IN,
1212
ARRAY,
1313
UNARY,
14+
FIELD,
1415
ARITHMETIC,
15-
ARITHMETIC_LEAF,
16-
ARITHMETIC_UNARY,
17-
ARITHMETIC_FUNCTION,
18-
STRING
16+
ARITHMETIC_FUNCTION
1917
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.github.sidhant92.boolparser.domain;
2+
3+
import com.github.sidhant92.boolparser.constant.DataType;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.Setter;
8+
9+
@AllArgsConstructor
10+
@Getter
11+
@Setter
12+
@Builder
13+
public class EvaluatedNode {
14+
private final DataType dataType;
15+
16+
private final Object value;
17+
}

0 commit comments

Comments
 (0)