View Javadoc
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 }