Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ out/
`

gradle.properties
src/**/*.peg
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,16 @@ assertTrue(booleanOptional.get());

The return type is `Optional<Boolean>`. If its absent which means parsing has failed and any fallback can be used.

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

### Contribution
We use [Canopy](http://canopy.jcoglan.com/) which is a PEG parser compiler. You can make changes to the grammar and generate the code using the following steps. It will be better if you read its documentation first.
1. install canopy using `npm install -g canopy`.
1. you might need to fix the canopy(0.3.0) file(in the node_modules) depending on the node version. try following the next steps and if it throws some error related to `mkdirp` put the write function in(or around) line 32 in the `then` part of the promise.
2. copy `canopy.peg` file to `$APP_HOME/src/main/java/com/github/sidhant92/boolparser/parser`.
3. go to dir `$APP_HOME/src/main/java`
4. run `canopy com/github/sidhant92/boolparser/parser/canopy.peg --lang java`

#### To run tests
Make a `gradle.properties` file in `$APP_HOME` and copy the contents of `gradle.properties.sample` file there.
Change the java home location as per your OS/java-version. For rest of the fields you can put dummy data.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ dependencies {
compile 'ch.qos.logback.contrib:logback-jackson:0.1.5'
compile 'net.logstash.logback:logstash-logback-encoder:5.2'
compile 'org.apache.maven:maven-artifact:3.5.2'
compile 'com.google.guava:guava:30.1-jre'
compile 'org.apache.commons:commons-collections4:4.1'


compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
Expand Down
35 changes: 35 additions & 0 deletions canopy.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
grammar Filters
start <- logical_or
logical_or <- logical_and (ws+ or ws+ logical_and)* %make_logical_or
logical_and <- primary (ws+ and ws+ primary)* %make_logical_and
primary <- token / "(" logical_or ")" %make_primary
token <- "NOT"? ws* alphanumeric ws* "=" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* ">" ws* app_version %make_app_version_token
/ "NOT"? ws* alphanumeric ws* ">=" ws* app_version %make_app_version_token
/ "NOT"? ws* alphanumeric ws* "<" ws* app_version %make_app_version_token
/ "NOT"? ws* alphanumeric ws* "<=" ws* app_version %make_app_version_token
/ "NOT"? ws* alphanumeric ws* ">" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* ">=" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* "<" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* "<=" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* "!=" ws* decimal %make_numeric_token
/ "NOT"? ws* alphanumeric ws* ":" ws* decimal ws* ("TO" / "to") ws* decimal %make_numeric_range_token
/ "NOT"? ws* alphanumeric ws* ":" ws* alphanumeric %make_string_token
/ "NOT"? ws* alphanumeric ws* in ws* "(" number_list ")" %make_decimal_list
/ "NOT"? ws* alphanumeric ws* in ws* "(" string_list ")" %make_string_list
/ "NOT"? ws* alphanumeric ws* rev_all ws* "(" number_list ")" %make_rev_all_decimal_list
/ "NOT"? ws* alphanumeric ws* rev_all ws* "(" string_list ")" %make_rev_all_string_list
number_list <- ws* decimal ws* number_list_token*
number_list_token <- "," ws* decimal ws*
string_list <- ws* alphanumeric ws* string_list_token*
string_list_token <- "," ws* alphanumeric ws*
app_version <- [0-9]+ "." [0-9]+ app_version_token+
app_version_token <- "." [0-9]+
alphanumeric <- [^ \t'"<>=:(),]+ / ["]+ [^"]+ ["]+ / [']+ [^']+ [']+
decimal <- [0-9]+ "."? [0-9]*
or <- ("OR" / "or" / "|" / "||")
and <- ("AND" / "and" / "&" / "&&")
operator <- ("=" / ">" / ">=" / "<" / "<=" / "!=")
in <- ("IN" / "in")
rev_all <- (`rev_all`)
ws <- [ \t]
2 changes: 1 addition & 1 deletion gradle.properties.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ ossrhPassword=
signing.keyId=
signing.password=@
signing.secretKeyRingFile=
org.gradle.java.home=
org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.sidhant92.boolparser.domain.Node;
import com.github.sidhant92.boolparser.domain.NumericRangeToken;
import com.github.sidhant92.boolparser.domain.NumericToken;
import com.github.sidhant92.boolparser.domain.ReverseAllMatchToken;
import com.github.sidhant92.boolparser.domain.StringToken;
import com.github.sidhant92.boolparser.operator.OperatorService;
import com.github.sidhant92.boolparser.parser.BoolExpressionParser;
Expand Down Expand Up @@ -45,6 +46,8 @@ private boolean evaluateNode(final Node node, final Map<String, Object> data) {
return evaluateNumericRangeToken((NumericRangeToken) node, data);
case BOOL_EXPRESSION:
return evaluateBooleanNode((BoolExpression) node, data);
case REVERSE_ALL_MATCH_TOKEN:
return evaluateReverseAllMatchToken((ReverseAllMatchToken) node, data);
default:
return false;
}
Expand Down Expand Up @@ -78,6 +81,15 @@ private boolean evaluateNumericRangeToken(final NumericRangeToken numericRangeTo
numericRangeToken.getToValue());
}

private boolean evaluateReverseAllMatchToken(final ReverseAllMatchToken token, final Map<String, Object> data) {
if (checkFieldDataMissing(token.getField(), data)) {
return false;
}
final Object fieldData = data.get(token.getField());
return operatorService.evaluate(Operator.REVERSE_MATCH_ALL, ContainerDataType.set, token.getDataType(), fieldData, token.getValues());
}


private boolean evaluateBooleanNode(final BoolExpression boolExpression, final Map<String, Object> data) {
return (boolExpression.getOrOperations().stream().anyMatch(orOperation -> evaluateNode(orOperation, data)) || boolExpression.getOrOperations()
.isEmpty()) && (boolExpression
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.github.sidhant92.boolparser.constant;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.github.sidhant92.boolparser.datatype.DataTypeFactory;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -12,7 +17,7 @@
@Getter
@AllArgsConstructor
public enum ContainerDataType {
primitive() {
primitive(null) {
@Override
public <T> Optional<T> getValue(final DataType dataType, final Object value) {
return DataTypeFactory.getDataType(dataType).getValue(value);
Expand All @@ -22,8 +27,56 @@ public <T> Optional<T> getValue(final DataType dataType, final Object value) {
public boolean isValid(final DataType dataType, final Object value) {
return DataTypeFactory.getDataType(dataType).isValid(value);
}
},
set(Set.class) {
@Override
public Optional<Set<?>> getValue(final DataType dataType, final Object value) {
final CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(this.getClassType(),
DataTypeFactory.getDataType(dataType).getClassType());
Set<?> valueSet = objectMapper.convertValue(value, collectionType);
return Optional.ofNullable(valueSet);
}

@Override
public boolean isValid(final DataType dataType, final Object value) {
return isValidCollectionType(dataType, value);
}
},
list(List.class) {
@Override
public Optional<List<?>> getValue(final DataType dataType, final Object value) {
final CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(this.getClassType(),
DataTypeFactory.getDataType(dataType).getClassType());
List<?> valueSet = objectMapper.convertValue(value, collectionType);
return Optional.ofNullable(valueSet);
}

@Override
public boolean isValid(final DataType dataType, final Object value) {
return isValidCollectionType(dataType, value);
}
};

private static ObjectMapper objectMapper;

private Class<? extends Collection> classType;

public static void init(final ObjectMapper objectMapper) {
ContainerDataType.objectMapper = objectMapper;
}

protected boolean isValidCollectionType(final DataType dataType, final Object value) {
try {
final CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(getClassType(),
DataTypeFactory.getDataType(dataType).getClassType());
Collection<?> collection = objectMapper.convertValue(value, collectionType);
return collection != null && collection.stream().allMatch(DataTypeFactory.getDataType(dataType)::isValid);

} catch (final Exception ex) {
return false;
}
}

public abstract <T> Optional<T> getValue(final DataType dataType, final Object value);

public abstract boolean isValid(final DataType dataType, final Object value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public enum NodeType {
BOOL_EXPRESSION,
STRING_TOKEN,
NUMERIC_TOKEN,
NUMERIC_RANGE_TOKEN
NUMERIC_RANGE_TOKEN,
REVERSE_ALL_MATCH_TOKEN
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ public enum Operator {
GREATER_THAN_EQUAL,
LESS_THAN,
LESS_THAN_EQUAL,
NOT_EQUAL;
NOT_EQUAL,
REVERSE_MATCH_ALL;

public static Optional<Operator> getOperatorFromSymbol(final String symbol) {
final List<AbstractOperator> abc = OperatorFactory.getAllOperators();
return OperatorFactory.getAllOperators().stream().filter(operator -> operator.getSymbol().equals(symbol)).map(AbstractOperator::getOperator)
.findFirst();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ public Optional<T> defaultGetValue(final Object value, final ObjectMapper object
public abstract boolean isValid(final Object value);

public abstract Optional<T> getValue(final Object value);

public Class<T> getClassType() {
return clazz;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.EnumMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;

/**
Expand All @@ -18,6 +19,7 @@ private DataTypeFactory() {

public static void initialize() {
final ObjectMapper objectMapper = new ObjectMapper();
ContainerDataType.init(objectMapper);
abstractDataTypeMap.put(DataType.STRING, new StringDataType(objectMapper));
abstractDataTypeMap.put(DataType.INTEGER, new IntegerDataType(objectMapper));
abstractDataTypeMap.put(DataType.DECIMAL, new DecimalDataType(objectMapper));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.sidhant92.boolparser.domain;

import java.util.List;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.NodeType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@AllArgsConstructor
@Getter
@Setter
@Builder
public class ReverseAllMatchToken extends Node{
final String field;

final List<Object> values;

final DataType dataType;

@Override
public NodeType getNodeType() {
return NodeType.REVERSE_ALL_MATCH_TOKEN;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public static void initialize() {
operatorMap.put(Operator.LESS_THAN, new LessThanOperator());
operatorMap.put(Operator.LESS_THAN_EQUAL, new LessThanEqualOperator());
operatorMap.put(Operator.NOT_EQUAL, new NotEqualsOperator());
operatorMap.put(Operator.REVERSE_MATCH_ALL, new ReverseMatchAllOperator());
}

public static AbstractOperator getOperator(final Operator operator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.sidhant92.boolparser.operator;

import java.util.Optional;
import java.util.Set;
import org.apache.commons.collections4.SetUtils;
import com.github.sidhant92.boolparser.constant.ContainerDataType;
import com.github.sidhant92.boolparser.constant.DataType;
import com.github.sidhant92.boolparser.constant.Operator;

public class ReverseMatchAllOperator extends AbstractOperator {
@Override
public <T extends Comparable<? super T>> boolean evaluate(final ContainerDataType containerDataType, final DataType dataType,
final Object leftOperand, final Object... rightOperands) {
assert containerDataType.getClassType() == Set.class;
final Optional<Set<?>> leftValueOptional = containerDataType.getValue(dataType, leftOperand);
final Optional<Set<?>> rightValueOptional = containerDataType.getValue(dataType, rightOperands[0]);
return leftValueOptional.flatMap(leftValue -> rightValueOptional.map(rightValue -> SetUtils.difference(leftValue,
rightValue).isEmpty())).orElse(false);
}

@Override
public Operator getOperator() {
return Operator.REVERSE_MATCH_ALL;
}

@Override
public String getSymbol() {
return "rev_all";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface Actions {
TreeNode make_primary(String input, int start, int end, List<TreeNode> elements);
TreeNode make_string_list(String input, int start, int end, List<TreeNode> elements);
TreeNode make_string_token(String input, int start, int end, List<TreeNode> elements);
TreeNode make_rev_all_decimal_list(String input, int start, int end, List<TreeNode> elements);
TreeNode make_rev_all_string_list(String input, int start, int end, List<TreeNode> elements);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.github.sidhant92.boolparser.parser.canopy.domain.NumericRangeNode;
import com.github.sidhant92.boolparser.parser.canopy.domain.NumericNode;
import com.github.sidhant92.boolparser.parser.canopy.domain.BooleanNode;
import com.github.sidhant92.boolparser.parser.canopy.domain.ReverseAllMatchNode;
import com.github.sidhant92.boolparser.parser.canopy.domain.StringNode;

/**
Expand Down Expand Up @@ -75,6 +76,7 @@ public TreeNode make_numeric_range_token(String input, int start, int end, List<
return checkNotExpression(elements, numericRangeNode);
}

@Override
public TreeNode make_primary(String input, int start, int end, List<TreeNode> elements) {
return elements.get(1);
}
Expand Down Expand Up @@ -106,6 +108,7 @@ private String getCapturedString(final String input) {
}
}

@Override
public TreeNode make_logical_and(String input, int start, int end, List<TreeNode> elements) {
BooleanNode booleanNode = new BooleanNode();
if (elements.get(1).iterator().hasNext()) {
Expand All @@ -121,6 +124,7 @@ public TreeNode make_logical_and(String input, int start, int end, List<TreeNode
}
}

@Override
public TreeNode make_logical_or(String input, int start, int end, List<TreeNode> elements) {
BooleanNode booleanNode = new BooleanNode();
if (elements.get(0) instanceof BooleanNode) {
Expand All @@ -145,6 +149,7 @@ public TreeNode make_logical_or(String input, int start, int end, List<TreeNode>
}
}

@Override
public TreeNode make_decimal_list(String input, int start, int end, List<TreeNode> elements) {
final BooleanNode booleanNode = new BooleanNode();
final List<String> list = Arrays.stream(elements.get(7).text.split(",")).map(String::trim).collect(Collectors.toList());
Expand Down Expand Up @@ -188,6 +193,7 @@ private Optional<Integer> parseInteger(final String number) {
}
}

@Override
public TreeNode make_string_list(String input, int start, int end, List<TreeNode> elements) {
final BooleanNode booleanNode = new BooleanNode();
final List<String> list = getAllAlphanumericTokens(elements.get(7));
Expand All @@ -202,4 +208,20 @@ private List<String> getAllAlphanumericTokens(final TreeNode treeNode) {
treeNode.elements.forEach(node -> tokens.addAll(getAllAlphanumericTokens(node)));
return tokens;
}

@Override
public TreeNode make_rev_all_decimal_list(String input, int start, int end, List<TreeNode> elements) {
final List<String> list = Arrays.stream(elements.get(7).text.split(",")).map(String::trim).collect(Collectors.toList());
final DataType dataType = findNumericDataTypeForList(list);
final List<Object> data = list.stream().map(d -> getValue(d, dataType)).collect(Collectors.toList());
final ReverseAllMatchNode node = new ReverseAllMatchNode(elements.get(2).text, data, dataType);
return checkNotExpression(elements, node);
}

@Override
public TreeNode make_rev_all_string_list(String input, int start, int end, List<TreeNode> elements) {
final List<String> list = getAllAlphanumericTokens(elements.get(7));
final ReverseAllMatchNode node = new ReverseAllMatchNode(elements.get(2).text, Arrays.asList(list.toArray()), DataType.STRING);
return checkNotExpression(elements, node);
}
}
Loading