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