-
Notifications
You must be signed in to change notification settings - Fork 38
/
BaseRuntime.java
169 lines (153 loc) · 5.25 KB
/
BaseRuntime.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
package io.burt.jmespath;
import java.util.List;
import java.util.Collection;
import io.burt.jmespath.parser.ExpressionParser;
import io.burt.jmespath.function.FunctionRegistry;
import io.burt.jmespath.function.Function;
import io.burt.jmespath.function.ArgumentTypeException;
import io.burt.jmespath.node.NodeFactory;
import io.burt.jmespath.node.StandardNodeFactory;
/**
* This class can be extended instead of implementing {@link Adapter} directly,
* in order to not have to implement a few of the methods that have non-specific
* implementations, like {@link Adapter#functionRegistry}, {@link Adapter#typeOf}
* or the {@link Comparable} interface. Subclasses are encouraged to override
* these methods if they have more efficient means to perform the same job.
*/
public abstract class BaseRuntime<T> implements Adapter<T> {
private final FunctionRegistry functionRegistry;
private final NodeFactory<T> nodeFactory;
private final boolean silentTypeErrors;
/**
* Create a new runtime with a default function registry.
*/
public BaseRuntime() {
this(RuntimeConfiguration.defaultConfiguration());
}
/**
* Create a new runtime with configuration.
*/
public BaseRuntime(RuntimeConfiguration configuration) {
this.silentTypeErrors = configuration.silentTypeErrors();
this.functionRegistry = configuration.functionRegistry();
this.nodeFactory = new StandardNodeFactory<>(this);
}
@Override
public Expression<T> compile(String expression) {
return ExpressionParser.fromString(this, expression);
}
/**
* Basic implementation of {@link Adapter#compare}.
* <p>
* Subclasses should override this method if they have a more efficient way to
* compare booleans, numbers and strings than to convert them to Java types
* using {@link Adapter#isTruthy}, {@link Adapter#toNumber},
* {@link Adapter#toString}, etc.
* <p>
* This only implements {@link java.util.Comparator#compare} fully for
* <code>null</code>, <code>number</code> and <code>string</code>, for
* <code>boolean</code> <code>array</code> and <code>object</code> it only
* does equality – specifically this means that it will return 0 for equal
* booleans, objects or arrays, and -1 otherwise. The reason is that JMESPath
* doesn't have any mechanisms for comparing objects or arrays, and doesn't
* define how objects and arrays should be compared.
* <p>
* When the arguments are not of the same type -1 is returned.
*/
@Override
public int compare(T value1, T value2) {
JmesPathType type1 = typeOf(value1);
JmesPathType type2 = typeOf(value2);
if (type1 == type2) {
switch (type1) {
case NULL:
return 0;
case BOOLEAN:
return isTruthy(value1) == isTruthy(value2) ? 0 : -1;
case NUMBER:
double d1 = toNumber(value1).doubleValue();
double d2 = toNumber(value2).doubleValue();
return Double.compare(d1, d2);
case STRING:
String s1 = toString(value1);
String s2 = toString(value2);
return s1.compareTo(s2);
case ARRAY:
return deepEqualsArray(value1, value2) ? 0 : -1;
case OBJECT:
return deepEqualsObject(value1, value2) ? 0 : -1;
default:
throw new IllegalStateException(String.format("Unknown node type encountered: %s", value1.getClass().getName()));
}
} else {
return -1;
}
}
private boolean deepEqualsArray(T value1, T value2) {
List<T> values1 = toList(value1);
List<T> values2 = toList(value2);
int size = values1.size();
if (size != values2.size()) {
return false;
}
for (int i = 0; i < size; i++) {
if (compare(values1.get(i), values2.get(i)) != 0) {
return false;
}
}
return true;
}
private boolean deepEqualsObject(T value1, T value2) {
Collection<T> keys1 = getPropertyNames(value1);
Collection<T> keys2 = getPropertyNames(value2);
if (keys1.size() != keys2.size()) {
return false;
}
if (!keys1.containsAll(keys2)) {
return false;
}
for (T key : keys1) {
if (compare(getProperty(value1, key), getProperty(value2, key)) != 0) {
return false;
}
}
return true;
}
/**
* Throws {@link ArgumentTypeException} unless {@link RuntimeConfiguration#silentTypeErrors}
* is true, in which case it returns a null value (<em>not</em> Java <code>null</code>).
*/
@Override
public T handleArgumentTypeError(Function function, String expectedType, String actualType) {
if (silentTypeErrors) {
return createNull();
} else {
throw new ArgumentTypeException(function, expectedType, actualType);
}
}
@Override
public FunctionRegistry functionRegistry() {
return functionRegistry;
}
@Override
public NodeFactory<T> nodeFactory() {
return nodeFactory;
}
/**
* Required method from the {@link java.util.Comparator} interface, returns
* true when the argument is of the same class, or a subclass of, the receiver.
*/
@Override
public boolean equals(Object o) {
return getClass().isInstance(o);
}
@Override
public int hashCode() {
return 31;
}
@Override
@Deprecated
public T getProperty(T value, String name) {
return getProperty(value, createString(name));
}
}