Skip to content

Commit 1d6f415

Browse files
author
tricolor2
committed
Added unary comparator support for isnull/notnull
1 parent 402f180 commit 1d6f415

15 files changed

+427
-63
lines changed

src/main/java/cz/jirutka/rsql/parser/RSQLParser.java

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
import cz.jirutka.rsql.parser.ast.Node;
2828
import cz.jirutka.rsql.parser.ast.NodesFactory;
2929
import cz.jirutka.rsql.parser.ast.RSQLOperators;
30-
import net.jcip.annotations.Immutable;
31-
30+
import cz.jirutka.rsql.parser.ast.UnaryComparisonOperator;
3231
import java.io.ByteArrayInputStream;
3332
import java.io.InputStream;
3433
import java.nio.charset.Charset;
3534
import java.util.Set;
35+
import net.jcip.annotations.Immutable;
3636

3737
/**
3838
* Parser of the RSQL (RESTful Service Query Language).
@@ -43,30 +43,33 @@
4343
*
4444
* <p><b>Grammar in EBNF notation:</b>
4545
* <pre>{@code
46-
* input = or, EOF;
47-
* or = and, { ( "," | " or " ) , and };
48-
* and = constraint, { ( ";" | " and " ), constraint };
49-
* constraint = ( group | comparison );
50-
* group = "(", or, ")";
46+
* input = or, EOF;
47+
* or = and, { ( "," | " or " ) , and };
48+
* and = constraint, { ( ";" | " and " ), constraint };
49+
* constraint = ( group | comparison );
50+
* group = "(", or, ")";
5151
*
52-
* comparison = selector, comparator, arguments;
53-
* selector = unreserved-str;
52+
* comparison = selector, comparator-node;
53+
* comparator-node = (comparator , arguments) | (comp-cust, arguments-cust);
54+
* selector = unreserved-str;
5455
*
55-
* comparator = comp-fiql | comp-alt;
56-
* comp-fiql = ( ( "=", { ALPHA } ) | "!" ), "=";
57-
* comp-alt = ( ">" | "<" ), [ "=" ];
56+
* comparator = comp-fiql | comp-alt;
57+
* comp-fiql = ("==", "!=");
58+
* comp-alt = ( ">" | "<" ), [ "=" ];
59+
* comp-cust = "=" ALPHA { ALPHA } "=";
5860
*
59-
* arguments = ( "(", value, { "," , value }, ")" ) | value;
60-
* value = unreserved-str | double-quoted | single-quoted;
61+
* arguments = ( "(", value, { "," , value }, ")" ) | value;
62+
* arguments-cust = ( "(", value, { "," , value }, ")" ) | [value];
63+
* value = unreserved-str | double-quoted | single-quoted;
6164
*
62-
* unreserved-str = unreserved, { unreserved }
63-
* single-quoted = "'", { ( escaped | all-chars - ( "'" | "\" ) ) }, "'";
64-
* double-quoted = '"', { ( escaped | all-chars - ( '"' | "\" ) ) }, '"';
65+
* unreserved-str = unreserved, { unreserved }
66+
* single-quoted = "'", { ( escaped | all-chars - ( "'" | "\" ) ) }, "'";
67+
* double-quoted = '"', { ( escaped | all-chars - ( '"' | "\" ) ) }, '"';
6568
*
66-
* reserved = '"' | "'" | "(" | ")" | ";" | "," | "=" | "!" | "~" | "<" | ">" | " ";
67-
* unreserved = all-chars - reserved;
68-
* escaped = "\", all-chars;
69-
* all-chars = ? all unicode characters ?;
69+
* reserved = '"' | "'" | "(" | ")" | ";" | "," | "=" | "!" | "~" | "<" | ">" | " ";
70+
* unreserved = all-chars - reserved;
71+
* escaped = "\", all-chars;
72+
* all-chars = ? all unicode characters ?;
7073
* }</pre>
7174
*
7275
* @version 2.1
@@ -80,23 +83,24 @@ public final class RSQLParser {
8083

8184

8285
/**
83-
* Creates a new instance of {@code RSQLParser} with the default set of comparison operators.
86+
* Creates a new instance of {@code RSQLParser} with the default set of comparison and unary operators.
8487
*/
8588
public RSQLParser() {
86-
this.nodesFactory = new NodesFactory(RSQLOperators.defaultOperators());
89+
this.nodesFactory = new NodesFactory(RSQLOperators.defaultComparisonOperators(), RSQLOperators.defaultUnaryOperator());
8790
}
8891

8992
/**
9093
* Creates a new instance of {@code RSQLParser} that supports only the specified comparison
9194
* operators.
9295
*
93-
* @param operators A set of supported comparison operators. Must not be <tt>null</tt> or empty.
96+
* @param comparisonOperators A set of supported comparison operators. Must not be <tt>null</tt> or empty.
97+
* @param unaryComparisonOperators A set of supported unary operators. Must not be <tt>null</tt> or empty.
9498
*/
95-
public RSQLParser(Set<ComparisonOperator> operators) {
96-
if (operators == null || operators.isEmpty()) {
99+
public RSQLParser(Set<ComparisonOperator> comparisonOperators, Set<UnaryComparisonOperator> unaryComparisonOperators) {
100+
if (comparisonOperators == null || comparisonOperators.isEmpty() || unaryComparisonOperators == null || unaryComparisonOperators.isEmpty()) {
97101
throw new IllegalArgumentException("operators must not be null or empty");
98102
}
99-
this.nodesFactory = new NodesFactory(operators);
103+
this.nodesFactory = new NodesFactory(comparisonOperators, unaryComparisonOperators);
100104
}
101105

102106
/**

src/main/java/cz/jirutka/rsql/parser/ast/NoArgRSQLVisitorAdapter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public abstract class NoArgRSQLVisitorAdapter<R> implements RSQLVisitor<R, Void>
3737

3838
public abstract R visit(ComparisonNode node);
3939

40+
public abstract R visit(UnaryComparisonNode node);
41+
4042

4143
public R visit(AndNode node, Void param) {
4244
return visit(node);
@@ -49,4 +51,6 @@ public R visit(OrNode node, Void param) {
4951
public R visit(ComparisonNode node, Void param) {
5052
return visit(node);
5153
}
54+
55+
public R visit(UnaryComparisonNode node, Void param){ return visit(node); }
5256
}

src/main/java/cz/jirutka/rsql/parser/ast/NodesFactory.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,21 @@
3838
public class NodesFactory {
3939

4040
private final Map<String, ComparisonOperator> comparisonOperators;
41+
private final Map<String, UnaryComparisonOperator> unaryComparisonOperators;
4142

43+
public NodesFactory(Set<ComparisonOperator> comparisonOperators, Set<UnaryComparisonOperator> unaryOperators) {
4244

43-
public NodesFactory(Set<ComparisonOperator> operators) {
45+
this.comparisonOperators = new HashMap<>(comparisonOperators.size());
46+
for (ComparisonOperator op : comparisonOperators) {
47+
for (String sym : op.getSymbols()) {
48+
this.comparisonOperators.put(sym, op);
49+
}
50+
}
4451

45-
comparisonOperators = new HashMap<>(operators.size());
46-
for (ComparisonOperator op : operators) {
52+
this.unaryComparisonOperators = new HashMap<>(unaryOperators.size());
53+
for (UnaryComparisonOperator op : unaryOperators) {
4754
for (String sym : op.getSymbols()) {
48-
comparisonOperators.put(sym, op);
55+
this.unaryComparisonOperators.put(sym, op);
4956
}
5057
}
5158
}
@@ -88,4 +95,24 @@ public ComparisonNode createComparisonNode(
8895
throw new UnknownOperatorException(operatorToken);
8996
}
9097
}
98+
99+
/**
100+
* Creates a {@link UnaryComparisonNode} instance with the given parameters.
101+
*
102+
* @param operatorToken A textual representation of the comparison operator to be found in the
103+
* set of supported {@linkplain UnaryComparisonOperator operators}.
104+
* @param selector The selector that specifies the left side of the comparison.
105+
*
106+
* @throws UnknownOperatorException If no operator for the specified operator token exists.
107+
*/
108+
public UnaryComparisonNode createUnaryComparisonNode(
109+
String operatorToken, String selector) throws UnknownOperatorException {
110+
111+
UnaryComparisonOperator op = unaryComparisonOperators.get(operatorToken);
112+
if (op != null) {
113+
return new UnaryComparisonNode(op, selector);
114+
} else {
115+
throw new UnknownOperatorException(operatorToken);
116+
}
117+
}
91118
}

src/main/java/cz/jirutka/rsql/parser/ast/RSQLOperators.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,16 @@ public abstract class RSQLOperators {
4141
NOT_IN = new ComparisonOperator("=out=", true);
4242

4343

44-
public static Set<ComparisonOperator> defaultOperators() {
44+
public static Set<ComparisonOperator> defaultComparisonOperators() {
4545
return new HashSet<>(asList(EQUAL, NOT_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL,
4646
LESS_THAN, LESS_THAN_OR_EQUAL, IN, NOT_IN));
4747
}
48+
49+
public static final UnaryComparisonOperator
50+
IS_NULL = new UnaryComparisonOperator("=isnull="),
51+
NOT_NULL = new UnaryComparisonOperator("=notnull=");
52+
53+
public static Set<UnaryComparisonOperator> defaultUnaryOperator(){
54+
return new HashSet<>(asList(IS_NULL, NOT_NULL));
55+
}
4856
}

src/main/java/cz/jirutka/rsql/parser/ast/RSQLVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ public interface RSQLVisitor<R, A> {
3636
R visit(OrNode node, A param);
3737

3838
R visit(ComparisonNode node, A param);
39+
40+
R visit(UnaryComparisonNode node, A param);
3941
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package cz.jirutka.rsql.parser.ast;
2+
3+
import java.util.Objects;
4+
5+
public class UnaryComparisonNode extends AbstractNode {
6+
7+
private final UnaryComparisonOperator operator;
8+
private final String selector;
9+
10+
/**
11+
* @param operator Must not be <tt>null</tt>.
12+
* @param selector ust not be <tt>null</tt> or blank.
13+
*
14+
* @throws IllegalArgumentException If one of the conditions specified above it not met.
15+
*/
16+
public UnaryComparisonNode(UnaryComparisonOperator operator, String selector){
17+
Assert.notNull(operator, "operator must not be null");
18+
Assert.notBlank(selector, "selector must not be blank");
19+
20+
this.operator = operator;
21+
this.selector = selector;
22+
}
23+
24+
public <R, A> R accept(RSQLVisitor<R, A> visitor, A param) {
25+
return visitor.visit(this, param);
26+
}
27+
28+
public UnaryComparisonOperator getOperator(){ return operator; }
29+
30+
/**
31+
* Returns a copy of this node with the specified operator.
32+
*
33+
* @param newOperator Must not be <tt>null</tt>.
34+
*/
35+
public UnaryComparisonNode withOperator(UnaryComparisonOperator newOperator) {
36+
return new UnaryComparisonNode(newOperator, selector);
37+
}
38+
39+
public String getSelector() {
40+
return selector;
41+
}
42+
43+
/**
44+
* Returns a copy of this node with the specified selector.
45+
*
46+
* @param newSelector Must not be <tt>null</tt> or blank.
47+
*/
48+
public UnaryComparisonNode withSelector(String newSelector) {
49+
return new UnaryComparisonNode(operator, newSelector);
50+
}
51+
52+
@Override
53+
public String toString() {
54+
return selector + operator;
55+
}
56+
57+
@Override
58+
public boolean equals(Object o) {
59+
if (this == o) return true;
60+
if (o == null || getClass() != o.getClass()) return false;
61+
UnaryComparisonNode unaryNode = (UnaryComparisonNode) o;
62+
return Objects.equals(operator, unaryNode.operator) &&
63+
Objects.equals(selector, unaryNode.selector);
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
int result = selector.hashCode();
69+
result = 31 * result + operator.hashCode();
70+
return result;
71+
}
72+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cz.jirutka.rsql.parser.ast;
2+
3+
import static cz.jirutka.rsql.parser.ast.StringUtils.isBlank;
4+
5+
import java.util.regex.Pattern;
6+
import jdk.nashorn.internal.ir.annotations.Immutable;
7+
8+
@Immutable
9+
public final class UnaryComparisonOperator {
10+
11+
private static final Pattern SYMBOL_PATTERN = Pattern.compile("=[a-zA-Z]+=");
12+
13+
private final String[] symbols;
14+
15+
/**
16+
* @param symbols Textual representation of this operator (e.g. <tt><null></></tt>); the first item
17+
* is primary representation, any others are alternatives. Must match
18+
* <tt><[a-zA-Z]+></tt>.
19+
*
20+
* @throws IllegalArgumentException If the {@code symbols} is either <tt>null</tt>, empty,
21+
* or contain illegal symbols.
22+
*/
23+
public UnaryComparisonOperator(String symbols[]){
24+
Assert.notEmpty(symbols, "symbols must not be null or empty");
25+
for (String sym : symbols) {
26+
Assert.isTrue(isValidOperatorSymbol(sym), "symbol must match: %s", SYMBOL_PATTERN);
27+
}
28+
this.symbols = symbols.clone();
29+
}
30+
31+
/**
32+
* @see #UnaryComparisonOperator(String[])
33+
*/
34+
public UnaryComparisonOperator(String symbol) {
35+
this(new String[]{symbol});
36+
}
37+
38+
/**
39+
* @see #UnaryComparisonOperator(String[])
40+
*/
41+
public UnaryComparisonOperator(String symbol, String altSymbol) {
42+
this(new String[]{symbol, altSymbol});
43+
}
44+
45+
/**
46+
* Returns the primary representation of this operator.
47+
*/
48+
public String getSymbol() {
49+
return symbols[0];
50+
}
51+
52+
/**
53+
* Returns all representations of this operator. The first item is always the primary
54+
* representation.
55+
*/
56+
public String[] getSymbols() {
57+
return symbols.clone();
58+
}
59+
60+
/**
61+
* Whether the given string can represent an operator.
62+
* Note: Allowed symbols are limited by the RSQL syntax (i.e. parser).
63+
*/
64+
private boolean isValidOperatorSymbol(String str) {
65+
return !isBlank(str) && SYMBOL_PATTERN.matcher(str).matches();
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return getSymbol();
71+
}
72+
73+
@Override
74+
public boolean equals(Object o) {
75+
if (this == o) return true;
76+
if (o == null || getClass() != o.getClass()) return false;
77+
UnaryComparisonOperator that = (UnaryComparisonOperator) o;
78+
return getSymbol().equals(that.getSymbol());
79+
}
80+
81+
@Override
82+
public int hashCode() {
83+
return getSymbol().hashCode();
84+
}
85+
}

0 commit comments

Comments
 (0)