/
Expressions.java
202 lines (167 loc) · 5.96 KB
/
Expressions.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package org.immutables.criteria.constraints;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* A set of predefined utilities and factories for expressions like {@link Literal} or {@link Call}
*/
public final class Expressions {
private Expressions() {}
public static Path path(final String path) {
Preconditions.checkNotNull(path, "path");
return new Path() {
@Override
public String path() {
return path;
}
@Nullable
@Override
public <R, C> R accept(ExpressionBiVisitor<R, C> visitor, @Nullable C context) {
return visitor.visit(this, context);
}
};
}
public static Literal nullLiteral(Class<?> type) {
return NullLiteral.ofType(type);
}
public static Literal literal(final Object value) {
Preconditions.checkArgument(value != null, "Use nullLiteral() factory method for nulls");
return new Literal() {
@Override
public Object value() {
return value;
}
@Override
public Type valueType() {
return value.getClass();
}
@Nullable
@Override
public <R, C> R accept(ExpressionBiVisitor<R, C> visitor, @Nullable C context) {
return visitor.visit(this, context);
}
};
}
public static Expression and(Expression first, Expression second) {
return and(Arrays.asList(first, second));
}
public static Expression and(Iterable<? extends Expression> expressions) {
return reduce(Operators.AND, expressions);
}
public static Expression or(Expression first, Expression second) {
return or(Arrays.asList(first ,second));
}
public static Expression or(Iterable<? extends Expression> expressions) {
return reduce(Operators.OR, expressions);
}
private static Expression reduce(Operator operator, Iterable<? extends Expression> expressions) {
final Iterable<? extends Expression> filtered = Iterables.filter(expressions, e -> !isNil(e) );
final int size = Iterables.size(filtered);
if (size == 0) {
return nil();
} else if (size == 1) {
return filtered.iterator().next();
}
return call(operator, expressions);
}
/**
* Hacky (and temporary) reflection until we define proper sub-classes for criterias
* (to hide Expressional implementation).
*/
static Expression extract(Object object) {
Objects.requireNonNull(object, "object");
try {
Class<?> current = object.getClass();
while(current.getSuperclass() != null){
if (Arrays.stream(current.getDeclaredFields()).anyMatch(f -> f.getName().equals("context"))) {
Field field = current.getDeclaredField("context");
field.setAccessible(true);
CriteriaContext<?> context = (CriteriaContext<?>) field.get(object);
return context.expression();
}
current = current.getSuperclass();
}
} catch (NoSuchFieldException|IllegalAccessException e) {
throw new RuntimeException("No field in " + object.getClass().getName(), e);
}
throw new UnsupportedOperationException("No field context found in " + object.getClass().getName());
}
/**
* Converts a {@link ExpressionVisitor} into a {@link ExpressionBiVisitor} (with ignored payload).
*/
static <V> ExpressionBiVisitor<V, Void> toBiVisitor(ExpressionVisitor<V> visitor) {
return new ExpressionBiVisitor<V, Void>() {
@Override
public V visit(Call call, @Nullable Void context) {
return visitor.visit(call);
}
@Override
public V visit(Literal literal, @Nullable Void context) {
return visitor.visit(literal);
}
@Override
public V visit(Path path, @Nullable Void context) {
return visitor.visit(path);
}
};
}
/**
* Combines expressions <a href="https://en.wikipedia.org/wiki/Disjunctive_normal_form">Disjunctive normal form</a>
*/
public static Expression dnf(Operator operator, Expression existing, Expression newExpression) {
if (!(operator == Operators.AND || operator == Operators.OR)) {
throw new IllegalArgumentException(String.format("Expected %s for operator but got %s",
Arrays.asList(Operators.AND, Operators.OR), operator));
}
if (isNil(existing)) {
return DnfExpression.create().and(newExpression);
}
if (!(existing instanceof DnfExpression)) {
throw new IllegalStateException(String.format("Expected existing expression to be %s but was %s",
DnfExpression.class.getName(), existing.getClass().getName()));
}
@SuppressWarnings("unchecked")
final DnfExpression conjunction = (DnfExpression) existing;
return operator == Operators.AND ? conjunction.and(newExpression) : conjunction.or(newExpression);
}
public static Call not(Call call) {
return Expressions.call(Operators.NOT, call);
}
public static Call call(final Operator operator, Expression ... operands) {
return call(operator, ImmutableList.copyOf(operands));
}
public static Call call(final Operator operator, final Iterable<? extends Expression> operands) {
final List<Expression> ops = ImmutableList.copyOf(operands);
return new Call() {
@Override
public List<Expression> getArguments() {
return ops;
}
@Override
public Operator getOperator() {
return operator;
}
@Nullable
@Override
public <R, C> R accept(ExpressionBiVisitor<R, C> visitor, @Nullable C context) {
return visitor.visit(this, context);
}
};
}
/**
* Used as sentinel for {@code noop} expression.
*/
@SuppressWarnings("unchecked")
public static Expression nil() {
return NilExpression.INSTANCE;
}
public static boolean isNil(Expression expression) {
return expression == NilExpression.INSTANCE;
}
}