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 package gov.nist.secauto.metaschema.core.metapath;
28
29 import gov.nist.secauto.metaschema.core.metapath.antlr.metapath10Lexer;
30 import gov.nist.secauto.metaschema.core.metapath.antlr.metapath10Parser;
31 import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
32 import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean;
33 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
34 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
35 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
36 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IDecimalItem;
37 import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
38 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
39
40 import org.antlr.v4.runtime.CharStreams;
41 import org.antlr.v4.runtime.CommonTokenStream;
42 import org.antlr.v4.runtime.misc.ParseCancellationException;
43 import org.antlr.v4.runtime.tree.ParseTree;
44 import org.apache.logging.log4j.LogManager;
45 import org.apache.logging.log4j.Logger;
46
47 import java.io.ByteArrayOutputStream;
48 import java.io.IOException;
49 import java.io.PrintStream;
50 import java.nio.charset.StandardCharsets;
51 import java.util.Arrays;
52
53 import edu.umd.cs.findbugs.annotations.NonNull;
54 import edu.umd.cs.findbugs.annotations.Nullable;
55
56 public class MetapathExpression {
57
58 public enum ResultType {
59 NUMBER,
60 STRING,
61 BOOLEAN,
62 SEQUENCE,
63 NODE;
64 }
65
66 private static final Logger LOGGER = LogManager.getLogger(MetapathExpression.class);
67
68 @NonNull
69 public static final MetapathExpression CONTEXT_NODE = new MetapathExpression(".", ContextItem.instance());
70
71 private final String path;
72 @NonNull
73 private final IExpression node;
74
75
76
77
78
79
80
81
82
83
84 @NonNull
85 public static MetapathExpression compile(@NonNull String path) {
86 @NonNull MetapathExpression retval;
87 if (".".equals(path)) {
88 retval = CONTEXT_NODE;
89 } else {
90 try {
91 metapath10Lexer lexer = new metapath10Lexer(CharStreams.fromString(path));
92 CommonTokenStream tokens = new CommonTokenStream(lexer);
93 metapath10Parser parser = new metapath10Parser(tokens);
94 parser.removeErrorListeners();
95 parser.addErrorListener(new FailingErrorListener());
96
97 ParseTree tree = ObjectUtils.notNull(parser.expr());
98
99 if (LOGGER.isDebugEnabled()) {
100 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
101 try (PrintStream ps = new PrintStream(os, true, StandardCharsets.UTF_8)) {
102 CSTPrinter printer = new CSTPrinter(ps);
103 printer.print(tree, Arrays.asList(metapath10Parser.ruleNames));
104 ps.flush();
105 }
106 LOGGER.atDebug().log(String.format("Metapath CST:%n%s", os.toString(StandardCharsets.UTF_8)));
107 } catch (IOException ex) {
108 LOGGER.atError().withThrowable(ex).log("An unexpected error occured while closing the steam.");
109 }
110 }
111
112 IExpression expr = new BuildAstVisitor().visit(tree);
113
114 if (LOGGER.isDebugEnabled()) {
115 LOGGER.atDebug().log(String.format("Metapath AST:%n%s", ASTPrinter.instance().visit(expr)));
116 }
117 retval = new MetapathExpression(path, expr);
118 } catch (MetapathException | ParseCancellationException ex) {
119 String msg = String.format("Unable to compile Metapath '%s'", path);
120 LOGGER.atError().withThrowable(ex).log(msg);
121 throw new MetapathException(msg, ex);
122 }
123 }
124 return retval;
125 }
126
127
128
129
130
131
132
133
134
135 protected MetapathExpression(@NonNull String path, @NonNull IExpression expr) {
136 this.path = path;
137 this.node = expr;
138 }
139
140
141
142
143
144
145 public String getPath() {
146 return path;
147 }
148
149
150
151
152
153
154 @NonNull
155 protected IExpression getASTNode() {
156 return node;
157 }
158
159 @Override
160 public String toString() {
161 return ASTPrinter.instance().visit(getASTNode());
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180 @Nullable
181 public <T> T evaluateAs(@NonNull ResultType resultType) {
182 return evaluateAs(null, resultType);
183 }
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204 @Nullable
205 public <T> T evaluateAs(
206 @Nullable IItem focus,
207 @NonNull ResultType resultType) {
208 ISequence<?> result = evaluate(focus);
209 return toResultType(result, resultType);
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 @Nullable
236 public <T> T evaluateAs(
237 @NonNull IItem focus,
238 @NonNull ResultType resultType,
239 @NonNull DynamicContext dynamicContext) {
240 ISequence<?> result = evaluate(focus, dynamicContext);
241 return toResultType(result, resultType);
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270 @SuppressWarnings("PMD.NullAssignment")
271 @Nullable
272 protected <T> T toResultType(@NonNull ISequence<?> sequence, @NonNull ResultType resultType) {
273 Object result;
274 switch (resultType) {
275 case BOOLEAN:
276 result = FnBoolean.fnBoolean(sequence).toBoolean();
277 break;
278 case NODE:
279 result = FunctionUtils.getFirstItem(sequence, true);
280 break;
281 case NUMBER:
282 INumericItem numeric = FunctionUtils.toNumeric(sequence, true);
283 result = numeric == null ? null : numeric.asDecimal();
284 break;
285 case SEQUENCE:
286 result = sequence;
287 break;
288 case STRING:
289 IItem item = FunctionUtils.getFirstItem(sequence, true);
290 result = item == null ? "" : FnData.fnDataItem(item).asString();
291 break;
292 default:
293 throw new InvalidTypeMetapathException(null, String.format("unsupported result type '%s'", resultType.name()));
294 }
295
296 @SuppressWarnings("unchecked") T retval = (T) result;
297 return retval;
298 }
299
300
301
302
303
304
305
306
307
308
309
310 @NonNull
311 public <T extends IItem> ISequence<T> evaluate() {
312 return evaluate(null);
313 }
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 @SuppressWarnings("unchecked")
329 @NonNull
330 public <T extends IItem> ISequence<T> evaluate(
331 @Nullable IItem focus) {
332 return (ISequence<T>) evaluate(
333 focus,
334 StaticContext.builder()
335 .build().newDynamicContext());
336 }
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355 @SuppressWarnings("unchecked")
356 @NonNull
357 public <T extends IItem> ISequence<T> evaluate(
358 @Nullable IItem focus,
359 @NonNull DynamicContext dynamicContext) {
360 try {
361 return (ISequence<T>) getASTNode().accept(dynamicContext, ISequence.of(focus));
362 } catch (MetapathException ex) {
363 throw new MetapathException(
364 String.format("An error occurred while evaluating the expression '%s'.", getPath()), ex);
365 }
366 }
367 }