Skip to content

Commit

Permalink
Merge pull request #28 from sidhant92/arithmetic
Browse files Browse the repository at this point in the history
Support for Arithmetic Expressions Evaluation
  • Loading branch information
sidhant92 authored Mar 17, 2024
2 parents 467ac22 + 797980a commit 717d045
Show file tree
Hide file tree
Showing 40 changed files with 1,764 additions and 369 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.github.sidhant92.boolparser.application;

import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.Operator;
import com.github.sidhant92.boolparser.domain.StringNode;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticLeafNode;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticNode;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticUnaryNode;
import com.github.sidhant92.boolparser.domain.Node;
import com.github.sidhant92.boolparser.exception.UnsupportedToken;
import com.github.sidhant92.boolparser.operator.OperatorService;
import com.github.sidhant92.boolparser.parser.BoolExpressionParser;
import com.github.sidhant92.boolparser.util.ValueUtils;
import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;

/**
* @author sidhant.aggarwal
* @since 15/03/2024
*/
@Slf4j
public class ArithmeticExpressionEvaluator {
private final BoolExpressionParser boolExpressionParser;

private final OperatorService operatorService;

public ArithmeticExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
this.boolExpressionParser = boolExpressionParser;
operatorService = new OperatorService();
}

public Try<Object> evaluate(final String expression, final Map<String, Object> data) {
final Try<Node> tokenOptional = boolExpressionParser.parseExpression(expression, null);
return tokenOptional.map(node -> evaluateToken(node, data));
}

protected Object evaluate(final Node node, final Map<String, Object> data) {
return evaluateToken(node, data);
}

private Object evaluateToken(final Node node, final Map<String, Object> data) {
switch (node.getTokenType()) {
case ARITHMETIC:
return evaluateArithmeticToken((ArithmeticNode) node, data);
case ARITHMETIC_LEAF:
return evaluateArithmeticLeafToken((ArithmeticLeafNode) node, data);
case ARITHMETIC_UNARY:
return evaluateUnaryArithmeticToken((ArithmeticUnaryNode) node, data);
case STRING:
return evaluateStringToken((StringNode) node, data);
default:
log.error("unsupported token {}", node.getTokenType());
throw new UnsupportedToken();
}
}

private Object evaluateStringToken(final StringNode stringNode, final Map<String, Object> data) {
return ValueUtils.getValueFromMap(stringNode.getField(), data).orElse(stringNode.getField());
}

private Pair<Object, DataType> evaluateArithmeticLeafToken(final ArithmeticLeafNode arithmeticLeafNode, final Map<String, Object> data) {
final Optional<Object> fetchedValue = ValueUtils.getValueFromMap(arithmeticLeafNode.getOperand().toString(), data);
return fetchedValue
.map(o -> Pair.of(o, ValueUtils.getDataType(o)))
.orElseGet(() -> Pair.of(arithmeticLeafNode.getOperand(), arithmeticLeafNode.getDataType()));
}

private Object evaluateUnaryArithmeticToken(final ArithmeticUnaryNode arithmeticUnaryNode, final Map<String, Object> data) {
final Object resolvedValue = evaluateToken(arithmeticUnaryNode.getOperand(), data);
if (resolvedValue instanceof Pair) {
final Pair<Object, DataType> pair = (Pair<Object, DataType>) resolvedValue;
return operatorService.evaluateArithmeticOperator(pair.getLeft(), pair.getRight(), null, null, Operator.UNARY,
ContainerDataType.PRIMITIVE);
}
final DataType dataType = ValueUtils.getDataType(resolvedValue);
return operatorService.evaluateArithmeticOperator(resolvedValue, dataType, null, null, Operator.UNARY, ContainerDataType.PRIMITIVE);
}

private Object evaluateArithmeticToken(final ArithmeticNode arithmeticNode, final Map<String, Object> data) {
final Object leftValue = evaluateToken(arithmeticNode.getLeft(), data);
final Object rightValue = evaluateToken(arithmeticNode.getRight(), data);
if (leftValue instanceof Pair && rightValue instanceof Pair) {
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightPair.getLeft(), rightPair.getRight(),
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
} else if (leftValue instanceof Pair) {
final Pair<Object, DataType> leftPair = (Pair<Object, DataType>) leftValue;
final DataType rightDataType = ValueUtils.getDataType(rightValue);
return operatorService.evaluateArithmeticOperator(leftPair.getLeft(), leftPair.getRight(), rightValue, rightDataType,
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
} else if (rightValue instanceof Pair) {
final Pair<Object, DataType> rightPair = (Pair<Object, DataType>) rightValue;
final DataType leftDataType = ValueUtils.getDataType(leftValue);
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, rightPair.getLeft(), rightPair.getRight(),
arithmeticNode.getOperator(), ContainerDataType.PRIMITIVE);
} else {
final DataType leftDataType = ValueUtils.getDataType(leftValue);
final DataType rightDataType = ValueUtils.getDataType(rightValue);
return operatorService.evaluateArithmeticOperator(leftValue, leftDataType, rightValue, rightDataType, arithmeticNode.getOperator(),
ContainerDataType.PRIMITIVE);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.github.sidhant92.boolparser.domain.ComparisonNode;
import com.github.sidhant92.boolparser.domain.Node;
import com.github.sidhant92.boolparser.domain.UnaryNode;
import com.github.sidhant92.boolparser.domain.arithmetic.ArithmeticBaseNode;
import com.github.sidhant92.boolparser.exception.DataNotFoundException;
import com.github.sidhant92.boolparser.exception.HeterogeneousArrayException;
import com.github.sidhant92.boolparser.exception.InvalidUnaryOperand;
Expand All @@ -31,9 +32,12 @@ public class BooleanExpressionEvaluator {

private final OperatorService operatorService;

private final ArithmeticExpressionEvaluator arithmeticExpressionEvaluator;

public BooleanExpressionEvaluator(final BoolExpressionParser boolExpressionParser) {
this.boolExpressionParser = boolExpressionParser;
operatorService = new OperatorService();
arithmeticExpressionEvaluator = new ArithmeticExpressionEvaluator(boolExpressionParser);
}

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

private boolean evaluateComparisonToken(final ComparisonNode comparisonToken, final Map<String, Object> data) {
final Object fieldData = ValueUtils.getValueFromMap(comparisonToken.getField(), data).orElseThrow(DataNotFoundException::new);
return operatorService.evaluate(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(), fieldData,
comparisonToken.getValue());
final Object value = comparisonToken.getValue() instanceof ArithmeticBaseNode ? arithmeticExpressionEvaluator.evaluate(
(Node) comparisonToken.getValue(), data) : comparisonToken.getValue();
return operatorService.evaluateLogicalOperator(comparisonToken.getOperator(), ContainerDataType.PRIMITIVE, comparisonToken.getDataType(),
fieldData, value);
}

private boolean evaluateNumericRangeToken(final NumericRangeNode numericRangeToken, final Map<String, Object> data) {
final Object fieldData = ValueUtils.getValueFromMap(numericRangeToken.getField(), data).orElseThrow(DataNotFoundException::new);
return operatorService.evaluate(Operator.GREATER_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getFromDataType(), fieldData,
numericRangeToken.getFromValue()) && operatorService.evaluate(Operator.LESS_THAN_EQUAL,
ContainerDataType.PRIMITIVE,
numericRangeToken.getToDataType(), fieldData,
numericRangeToken.getToValue());
return operatorService.evaluateLogicalOperator(Operator.GREATER_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getFromDataType(),
fieldData, numericRangeToken.getFromValue()) && operatorService.evaluateLogicalOperator(
Operator.LESS_THAN_EQUAL, ContainerDataType.PRIMITIVE, numericRangeToken.getToDataType(), fieldData, numericRangeToken.getToValue());
}

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

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

private boolean evaluateUnaryToken(final UnaryNode unaryToken, final Map<String, Object> data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ public enum NodeType {
NUMERIC_RANGE,
IN,
ARRAY,
UNARY
UNARY,
ARITHMETIC,
ARITHMETIC_LEAF,
ARITHMETIC_UNARY,
STRING
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.sidhant92.boolparser.constant;

import java.util.Optional;
import com.github.sidhant92.boolparser.operator.AbstractOperator;
import com.github.sidhant92.boolparser.operator.logical.AbstractOperator;
import com.github.sidhant92.boolparser.operator.OperatorFactory;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
Expand All @@ -21,15 +21,32 @@ public enum Operator {
LESS_THAN_EQUAL,
NOT_EQUAL,
IN,

ADD,
SUBTRACT,
MULTIPLY,
DIVIDE,
MODULUS,
EXPONENT,
UNARY,

CONTAINS_ALL,
CONTAINS_ANY;

public static Optional<Operator> getOperatorFromSymbol(final String symbol) {
final String symbolLowerCase = symbol.toLowerCase();
return OperatorFactory.getAllOperators()
final Optional<Operator> operator = OperatorFactory.getAllLogicalOperators()
.stream()
.filter(operator -> operator.getSymbol().toLowerCase().equals(symbolLowerCase))
.filter(op -> op.getSymbol().toLowerCase().equals(symbolLowerCase))
.map(AbstractOperator::getOperator)
.findFirst();
if (operator.isPresent()) {
return operator;
}
return OperatorFactory.getAllArithmeticOperators()
.stream()
.filter(op -> op.getSymbol().toLowerCase().equals(symbolLowerCase))
.map(com.github.sidhant92.boolparser.operator.arithmetic.AbstractOperator::getOperator)
.findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.github.sidhant92.boolparser.domain;

import com.github.sidhant92.boolparser.constant.NodeType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* @author sidhant.aggarwal
* @since 16/03/2024
*/
@AllArgsConstructor
@Getter
@Setter
@Builder
public class StringNode extends Node {
private final String field;

@Override
public NodeType getTokenType() {
return NodeType.STRING;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.sidhant92.boolparser.domain.arithmetic;

import com.github.sidhant92.boolparser.domain.Node;

/**
* @author sidhant.aggarwal
* @since 16/03/2024
*/
public abstract class ArithmeticBaseNode extends Node {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.github.sidhant92.boolparser.domain.arithmetic;

import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.NodeType;
import com.github.sidhant92.boolparser.domain.Node;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* @author sidhant.aggarwal
* @since 15/03/2024
*/
@AllArgsConstructor
@Getter
@Setter
@Builder
public class ArithmeticLeafNode extends ArithmeticBaseNode {
private Object operand;

private DataType dataType;

@Override
public NodeType getTokenType() {
return NodeType.ARITHMETIC_LEAF;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.sidhant92.boolparser.domain.arithmetic;

import com.github.sidhant92.boolparser.constant.NodeType;
import com.github.sidhant92.boolparser.constant.Operator;
import com.github.sidhant92.boolparser.domain.Node;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* @author sidhant.aggarwal
* @since 15/03/2024
*/
@AllArgsConstructor
@Getter
@Setter
@Builder
public class ArithmeticNode extends ArithmeticBaseNode {
private Node left;

private Node right;

private final Operator operator;

@Override
public NodeType getTokenType() {
return NodeType.ARITHMETIC;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.github.sidhant92.boolparser.domain.arithmetic;

import com.github.sidhant92.boolparser.constant.NodeType;
import com.github.sidhant92.boolparser.domain.Node;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

/**
* @author sidhant.aggarwal
* @since 15/03/2024
*/
@AllArgsConstructor
@Getter
@Setter
@Builder
public class ArithmeticUnaryNode extends ArithmeticBaseNode {
private Node operand;

@Override
public NodeType getTokenType() {
return NodeType.ARITHMETIC_UNARY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.github.sidhant92.boolparser.exception;

public class UnsupportedToken extends RuntimeException {
public UnsupportedToken(final String message) {
super(message);
}

public UnsupportedToken() {
super();
}

@Override
public String getMessage() {
return "Unsupported Token";
}
}
Loading

0 comments on commit 717d045

Please sign in to comment.