Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ConditionParser {
private final TokensParser parser = new TokensParser();
private final ConditionNormaliser normaliser = new ConditionNormaliser();

public Condition parse(String conditionString) {
public Condition parse(String conditionString) throws ConditionSyntaxException {
return new Condition(normaliser.normalise(parser.parse(extractor.extract(conditionString))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package app.quickcase.sdk.spring.condition;

public class ConditionSyntaxException extends Exception {
public ConditionSyntaxException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ public enum TokenCharacter {
DOUBLE_QUOTE('"'),
PARENTHESIS_OPEN('('),
PARENTHESIS_CLOSE(')'),
COLON(':'),
DOT('.'),
EQUAL('='),
GREATER_THAN('>'),
LESS_THAN('<'),
SQUARE_BRACKET_OPEN('['),
SQUARE_BRACKET_CLOSE(']'),
UNDERSCORE('_');

// ASCII code
Expand All @@ -29,6 +34,8 @@ public enum TokenCharacter {

public static final TokenCharacter[] OPERATOR_SYMBOLS = new TokenCharacter[]{
EQUAL,
GREATER_THAN,
LESS_THAN,
};

public static final TokenCharacter[] GROUP_DELIMITERS = new TokenCharacter[]{
Expand All @@ -49,8 +56,8 @@ public static Boolean isText(int code) {
// a-z
return true;
}
if (code == DOT.code || code == UNDERSCORE.code) {
// ._
if (code == COLON.code || code == DOT.code || code == UNDERSCORE.code || code == SQUARE_BRACKET_OPEN.code || code == SQUARE_BRACKET_CLOSE.code) {
// :._[]
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import app.quickcase.sdk.spring.condition.ConditionNode;
import app.quickcase.sdk.spring.condition.tokens.Token;
import app.quickcase.sdk.spring.condition.tokens.parse.error.SyntaxException;
import app.quickcase.sdk.spring.condition.ConditionSyntaxException;
import app.quickcase.sdk.spring.condition.tokens.parse.state.ParsingState;

/**
Expand All @@ -18,7 +18,7 @@
*/
public class TokensParser {

public ConditionNode[] parse(Token[] tokens) {
public ConditionNode[] parse(Token[] tokens) throws ConditionSyntaxException {
ParsingState state = ParsingState.START;
ParsingContext context = new ParsingContext();

Expand All @@ -28,7 +28,7 @@ public ConditionNode[] parse(Token[] tokens) {
.filter((posState) -> posState.accept(token))
.findFirst();
if (nextState.isEmpty()) {
throw new SyntaxException(String.format(
throw new ConditionSyntaxException(String.format(
"Unexpected token %s, expected one of: %s",
token,
formatStates(nextPossibleStates)
Expand All @@ -42,7 +42,7 @@ public ConditionNode[] parse(Token[] tokens) {
// Validate final state
final ParsingState[] endStates = state.nextStates(context);
if (!Arrays.asList(endStates).contains(ParsingState.END)) {
throw new SyntaxException("Unexpected end of condition, expected one of: " + formatStates(endStates));
throw new ConditionSyntaxException("Unexpected end of condition, expected one of: " + formatStates(endStates));
}

return context.rootNodes();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
import app.quickcase.sdk.spring.condition.tokens.TextToken;
import app.quickcase.sdk.spring.condition.tokens.Token;
import app.quickcase.sdk.spring.condition.tokens.parse.ParsingContext;
import app.quickcase.sdk.spring.path.FieldPath;

class FieldPathStateHandler implements ParsingStateHandler {
private static final Pattern REGEX = Pattern.compile("^[a-zA-Z0-9._]+$");

@Override
public Boolean accept(Token token) {
return token instanceof TextToken && REGEX.matcher(token.value()).matches();
return token instanceof TextToken && FieldPath.accepts(token.value());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
import app.quickcase.sdk.spring.condition.tokens.parse.ParsingContext;

class OperatorStateHandler implements ParsingStateHandler {
private final String targetToken;
private final String[] acceptTokens;
private final ParsingState[] nextStates;

public OperatorStateHandler(String[] acceptTokens, ParsingState[] nextStates) {
this(null, acceptTokens, nextStates);
}

public OperatorStateHandler(String targetToken, String[] acceptTokens, ParsingState[] nextStates) {
this.targetToken = targetToken;
this.acceptTokens = acceptTokens;
this.nextStates = nextStates;
}
Expand All @@ -29,6 +35,6 @@ public ParsingState[] nextStates(ParsingContext context) {

@Override
public void apply(ParsingContext context, Token token) {
context.getCriteriaBuilder().operator(token.value());
context.getCriteriaBuilder().operator(targetToken != null ? targetToken : token.value());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,30 @@ public enum ParsingState {
new String[] {"ENDS_WITH_IC"},
new ParsingState[]{VALUE_STRING}
)),
COMP_GREATER_THAN(new OperatorStateHandler(
"GREATER_THAN",
new String[] {"GREATER_THAN", ">"},
new ParsingState[]{VALUE_NUMBER}
)),
COMP_GREATER_OR_EQUALS(new OperatorStateHandler(
"GREATER_OR_EQUALS",
new String[] {"GREATER_OR_EQUALS", ">="},
new ParsingState[]{VALUE_NUMBER}
)),
COMP_HAS_LENGTH(new OperatorStateHandler(
new String[]{"HAS_LENGTH"},
new ParsingState[]{VALUE_NUMBER}
)),
COMP_LESS_THAN(new OperatorStateHandler(
"LESS_THAN",
new String[] {"LESS_THAN", "<"},
new ParsingState[]{VALUE_NUMBER}
)),
COMP_LESS_OR_EQUALS(new OperatorStateHandler(
"LESS_OR_EQUALS",
new String[] {"LESS_OR_EQUALS", "<="},
new ParsingState[]{VALUE_NUMBER}
)),
COMP_MATCHES(new OperatorStateHandler(
new String[]{"MATCHES"},
new ParsingState[]{VALUE_STRING}
Expand All @@ -53,7 +73,11 @@ public enum ParsingState {
COMP_CONTAINS,
COMP_EQUALS,
COMP_ENDS_WITH,
COMP_GREATER_THAN,
COMP_GREATER_OR_EQUALS,
COMP_HAS_LENGTH,
COMP_LESS_THAN,
COMP_LESS_OR_EQUALS,
COMP_MATCHES,
COMP_STARTS_WITH,
};
Expand Down
18 changes: 6 additions & 12 deletions src/main/java/app/quickcase/sdk/spring/metadata/Metadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,14 @@ public static Metadata fromPath(@NonNull String path) {

var name = path.substring(1, path.length() - 1).toLowerCase();
return switch (name) {
case "workspace", "organisation", "jurisdiction" ->
WORKSPACE;
case "type", "case_type" ->
TYPE;
case "id", "reference", "case_reference" ->
ID;
case "workspace", "organisation", "jurisdiction" -> WORKSPACE;
case "type", "case_type" -> TYPE;
case "id", "reference", "case_reference" -> ID;
case "title" -> TITLE;
case "state" -> STATE;
case "classification", "security_classification" ->
CLASSIFICATION;
case "createdat", "created", "created_date" ->
CREATED_AT;
case "lastmodifiedat", "modified", "last_modified", "last_modified_date" ->
LAST_MODIFIED_AT;
case "classification", "security_classification" -> CLASSIFICATION;
case "createdat", "created", "created_date" -> CREATED_AT;
case "lastmodifiedat", "modified", "last_modified", "last_modified_date" -> LAST_MODIFIED_AT;
default -> throw new IllegalArgumentException("Invalid metadata path: " + path);
};
}
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/app/quickcase/sdk/spring/path/ComputedFieldPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package app.quickcase.sdk.spring.path;

import java.util.regex.Pattern;

import lombok.NonNull;

public class ComputedFieldPath extends FieldPath {
public static final Pattern PATTERN = Pattern.compile("^:[a-zA-Z0-9_]+$");

protected ComputedFieldPath(@NonNull String path) {
super(path);

if (!accepts(path)) {
throw new IllegalArgumentException("Invalid computed field path: " + path);
}
}

public static boolean accepts(String path) {
return PATTERN.matcher(path).matches();
}

public String getIdentifier() {
return path.substring(1);
}
}
12 changes: 10 additions & 2 deletions src/main/java/app/quickcase/sdk/spring/path/DataFieldPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import lombok.NonNull;

public final class DataFieldPath extends FieldPath {
public static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(?:\\[(?:(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+))?])?(?:\\.[a-zA-Z0-9_]+(?:\\[(?:(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+))?])?)*$");
public static final Pattern COLLECTION_ITEM_PATTERN = Pattern.compile("^(?<collectionId>[a-zA-Z0-9_]+)\\[(?:(?:id:(?<itemId>[a-zA-Z0-9_]+))|(?<itemIndex>[0-9]+))?]$");
private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9_]+(?:\\[(?:(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+))?])?(?:\\.[a-zA-Z0-9_]+(?:\\[(?:(?:id:[a-zA-Z0-9_]+)|(?:[0-9]+))?])?)*$");
private static final Pattern COLLECTION_ITEM_PATTERN = Pattern.compile("^(?<collectionId>[a-zA-Z0-9_]+)\\[(?:(?:id:(?<itemId>[a-zA-Z0-9_]+))|(?<itemIndex>[0-9]+))?]$");

DataFieldPath(@NonNull String path) {
super(path);

if (!accepts(path)) {
throw new IllegalArgumentException("Invalid data field path: " + path);
}
}

public List<Element> elements() {
Expand All @@ -21,6 +25,10 @@ public List<Element> elements() {
.toList();
}

public static boolean accepts(String path) {
return PATTERN.matcher(path).matches();
}

@Getter
public static class Element {
private final String identifier;
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/app/quickcase/sdk/spring/path/FieldPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ protected FieldPath(@NonNull String path) {
this.path = path;
}

public static boolean accepts(@NonNull String path) {
return MetadataFieldPath.accepts(path) || ComputedFieldPath.accepts(path) || DataFieldPath.accepts(path);
}

public static FieldPath of(@NonNull String path) {
if (MetadataFieldPath.PATTERN.matcher(path).matches()) {
if (MetadataFieldPath.accepts(path)) {
return ofMetadata(path);
}

if (ComputedFieldPath.accepts(path)) {
return ofComputed(path);
}

return ofData(path);
}

Expand All @@ -24,13 +32,13 @@ public static MetadataFieldPath ofMetadata(@NonNull String path) {
}

public static DataFieldPath ofData(@NonNull String path) {
if (!DataFieldPath.PATTERN.matcher(path).matches()) {
throw new IllegalArgumentException("Invalid data field path: " + path);
}

return new DataFieldPath(path);
}

public static ComputedFieldPath ofComputed(@NonNull String path) {
return new ComputedFieldPath(path);
}

@Override
public String toString() {
return path;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package app.quickcase.sdk.spring.path;

import java.util.regex.Pattern;
import java.util.Arrays;

import app.quickcase.sdk.spring.metadata.Metadata;
import lombok.Getter;
import lombok.NonNull;

@Getter
public final class MetadataFieldPath extends FieldPath {
public static final Pattern PATTERN = Pattern.compile("^\\[[a-zA-Z_]+]$");

final private Metadata metadata;

MetadataFieldPath(@NonNull String path) {
super(path);
metadata = Metadata.fromPath(path);
}

public static boolean accepts(@NonNull String path) {
return Arrays.stream(Metadata.values()).anyMatch(meta -> meta.getPath().equalsIgnoreCase(path));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ void nLevelGrouping() {

private void assertCondition(String conditionString, Condition expected) {
final ConditionParser parser = new ConditionParser();
assertThat(parser.parse(conditionString), equalTo(expected));
try {
assertThat(parser.parse(conditionString), equalTo(expected));
} catch (ConditionSyntaxException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,40 @@ private static Stream<Arguments> provideHappyTestCases() {
text("OR"),
text("NOT"), groupDelimiter("("), text("c"), operator("==="), number("3"), groupDelimiter(")")
)
),
args(
"Support for all field path syntaxes",
"[state] == \"active\" " +
"AND :computedField == 2" +
"AND complexField.member1 == \"test\" " +
"AND collectionField[0].value == \"itemValue\" " +
"AND collectionField[id:item1] == \"itemValue\"",
array(
text("[state]"), operator("=="), quotedString("active"),
text("AND"), text(":computedField"), operator("=="), number("2"),
text("AND"), text("complexField.member1"), operator("=="), quotedString("test"),
text("AND"), text("collectionField[0].value"), operator("=="), quotedString("itemValue"),
text("AND"), text("collectionField[id:item1]"), operator("=="), quotedString("itemValue")
)
),
args(
"Support for all operator symbols",
"field1 = 0 " +
"AND field2 == 0 " +
"AND field3 === 0 " +
"AND field4 > 0 " +
"AND field5 >= 0 " +
"AND field6 < 0 " +
"AND field7 <= 0",
array(
text("field1"), operator("="), number("0"),
text("AND"), text("field2"), operator("=="), number("0"),
text("AND"), text("field3"), operator("==="), number("0"),
text("AND"), text("field4"), operator(">"), number("0"),
text("AND"), text("field5"), operator(">="), number("0"),
text("AND"), text("field6"), operator("<"), number("0"),
text("AND"), text("field7"), operator("<="), number("0")
)
)
);
}
Expand Down
Loading