1 /*
2 * Portions of this software was developed by employees of the National Institute
3 * of Standards and Technology (NIST), an agency of the Federal Government and is
4 * being made available as a public service. Pursuant to title 17 United States
5 * Code Section 105, works of NIST employees are not subject to copyright
6 * protection in the United States. This software may be subject to foreign
7 * copyright. Permission in the United States and in foreign countries, to the
8 * extent that NIST may hold copyright, to use, copy, modify, create derivative
9 * works, and distribute this software and its documentation without fee is hereby
10 * granted on a non-exclusive basis, provided that this notice and disclaimer
11 * of warranty appears in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
19 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
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 * Compiles a Metapath expression string.
77 *
78 * @param path
79 * the metapath expression
80 * @return the compiled expression object
81 * @throws MetapathException
82 * if an error occurred while compiling the Metapath expression
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 * Construct a new Metapath expression.
129 *
130 * @param path
131 * the Metapath as a string
132 * @param expr
133 * the Metapath as a compiled abstract syntax tree (AST)
134 */
135 protected MetapathExpression(@NonNull String path, @NonNull IExpression expr) {
136 this.path = path;
137 this.node = expr;
138 }
139
140 /**
141 * Get the original Metapath expression as a string.
142 *
143 * @return the expression
144 */
145 public String getPath() {
146 return path;
147 }
148
149 /**
150 * Get the compiled abstract syntax tree (AST) representation of the Metapath.
151 *
152 * @return the Metapath AST
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 * Evaluate this Metapath expression without a specific focus. The required
166 * result type will be determined by the {@code resultType} argument.
167 *
168 * @param <T>
169 * the expected result type
170 * @param resultType
171 * the type of result to produce
172 * @return the converted result
173 * @throws TypeMetapathException
174 * if the provided sequence is incompatible with the requested result
175 * type
176 * @throws MetapathException
177 * if an error occurred during evaluation
178 * @see #toResultType(ISequence, ResultType)
179 */
180 @Nullable
181 public <T> T evaluateAs(@NonNull ResultType resultType) {
182 return evaluateAs(null, resultType);
183 }
184
185 /**
186 * Evaluate this Metapath expression using the provided {@code focus} as the
187 * initial evaluation context. The required result type will be determined by
188 * the {@code resultType} argument.
189 *
190 * @param <T>
191 * the expected result type
192 * @param focus
193 * the outer focus of the expression
194 * @param resultType
195 * the type of result to produce
196 * @return the converted result
197 * @throws TypeMetapathException
198 * if the provided sequence is incompatible with the requested result
199 * type
200 * @throws MetapathException
201 * if an error occurred during evaluation
202 * @see #toResultType(ISequence, ResultType)
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 * Evaluate this Metapath expression using the provided {@code focus} as the
214 * initial evaluation context. The specific result type will be determined by
215 * the {@code resultType} argument.
216 * <p>
217 * This variant allow for reuse of a provided {@code dynamicContext}.
218 *
219 * @param <T>
220 * the expected result type
221 * @param focus
222 * the outer focus of the expression
223 * @param resultType
224 * the type of result to produce
225 * @param dynamicContext
226 * the dynamic context to use for evaluation
227 * @return the converted result
228 * @throws TypeMetapathException
229 * if the provided sequence is incompatible with the requested result
230 * type
231 * @throws MetapathException
232 * if an error occurred during evaluation
233 * @see #toResultType(ISequence, ResultType)
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 * Converts the provided {@code sequence} to the requested {@code resultType}.
246 * <p>
247 * The {@code resultType} determines the returned result, which is derived from
248 * the evaluation result sequence, as follows:
249 * <ul>
250 * <li>BOOLEAN - the effective boolean result is produced using
251 * {@link FnBoolean#fnBoolean(ISequence)}.</li>
252 * <li>NODE - the first result item in the sequence is returned.</li>
253 * <li>NUMBER - the sequence is cast to a number using
254 * {@link IDecimalItem#cast(IAnyAtomicItem)}.</li>
255 * <li>SEQUENCE - the evaluation result sequence.</li>
256 * <li>STRING - the string value of the first result item in the sequence.</li>
257 * </ul>
258 *
259 * @param <T>
260 * the requested return value
261 * @param sequence
262 * the sequence to convert
263 * @param resultType
264 * the type of result to produce
265 * @return the converted result
266 * @throws TypeMetapathException
267 * if the provided sequence is incompatible with the requested result
268 * type
269 */
270 @SuppressWarnings("PMD.NullAssignment") // for readability
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 * Evaluate this Metapath expression without a specific focus.
302 *
303 * @param <T>
304 * the type of items contained in the resulting sequence
305 * @return a sequence of Metapath items representing the result of the
306 * evaluation
307 * @throws MetapathException
308 * if an error occurred during evaluation
309 */
310 @NonNull
311 public <T extends IItem> ISequence<T> evaluate() {
312 return evaluate(null);
313 }
314
315 /**
316 * Evaluate this Metapath expression using the provided {@code focus} as the
317 * initial evaluation context.
318 *
319 * @param <T>
320 * the type of items contained in the resulting sequence
321 * @param focus
322 * the outer focus of the expression
323 * @return a sequence of Metapath items representing the result of the
324 * evaluation
325 * @throws MetapathException
326 * if an error occurred during evaluation
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 * Evaluate this Metapath expression using the provided {@code focus} as the
340 * initial evaluation context.
341 * <p>
342 * This variant allow for reuse of a provided {@code dynamicContext}.
343 *
344 * @param <T>
345 * the type of items contained in the resulting sequence
346 * @param focus
347 * the outer focus of the expression
348 * @param dynamicContext
349 * the dynamic context to use for evaluation
350 * @return a sequence of Metapath items representing the result of the
351 * evaluation
352 * @throws MetapathException
353 * if an error occurred during evaluation
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) { // NOPMD - intentional
363 throw new MetapathException(
364 String.format("An error occurred while evaluating the expression '%s'.", getPath()), ex);
365 }
366 }
367 }