1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath;
7   
8   import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ConversionFunction;
9   import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
10  import gov.nist.secauto.metaschema.core.metapath.function.library.FnBoolean;
11  import gov.nist.secauto.metaschema.core.metapath.impl.LazyCompilationMetapathExpression;
12  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
14  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
15  import gov.nist.secauto.metaschema.core.metapath.item.atomic.INumericItem;
16  import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException;
17  import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException;
18  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19  
20  import java.math.BigDecimal;
21  
22  import edu.umd.cs.findbugs.annotations.NonNull;
23  import edu.umd.cs.findbugs.annotations.Nullable;
24  
25  /**
26   * Supports compiling and executing Metapath expressions.
27   */
28  public interface IMetapathExpression {
29  
30    /**
31     * Identifies the expected type for a Metapath evaluation result.
32     */
33    enum ResultType {
34      /**
35       * The result is expected to be a {@link BigDecimal} value.
36       */
37      NUMBER(BigDecimal.class, sequence -> {
38        INumericItem numeric = FunctionUtils.toNumeric(sequence, true);
39        return numeric == null ? null : numeric.asDecimal();
40      }),
41      /**
42       * The result is expected to be a {@link String} value.
43       */
44      STRING(String.class, sequence -> {
45        IAnyAtomicItem item = ISequence.of(sequence.atomize()).getFirstItem(true);
46        return item == null ? "" : item.asString();
47      }),
48      /**
49       * The result is expected to be a {@link Boolean} value.
50       */
51      BOOLEAN(Boolean.class, sequence -> FnBoolean.fnBoolean(sequence).toBoolean()),
52      /**
53       * The result is expected to be an {@link IItem} value.
54       */
55      ITEM(IItem.class, sequence -> sequence.getFirstItem(true));
56  
57      @NonNull
58      private final Class<?> clazz;
59      private final ConversionFunction converter;
60  
61      ResultType(@NonNull Class<?> clazz, @NonNull ConversionFunction converter) {
62        this.clazz = clazz;
63        this.converter = converter;
64      }
65  
66      /**
67       * Get the expected class for the result type.
68       *
69       * @return the expected class
70       *
71       */
72      @NonNull
73      public Class<?> expectedClass() {
74        return clazz;
75      }
76  
77      /**
78       * Convert the provided sequence to the expected type.
79       *
80       * @param <T>
81       *          the Java type of the expected return value
82       * @param sequence
83       *          the Metapath result sequence to convert
84       * @return the converted sequence as the expected type
85       * @throws TypeMetapathException
86       *           if the provided sequence is incompatible with the expected result
87       *           type
88       */
89      @Nullable
90      public <T> T convert(@NonNull ISequence<?> sequence) {
91        try {
92          return ObjectUtils.asNullableType(converter.convert(sequence));
93        } catch (ClassCastException ex) {
94          throw new InvalidTypeMetapathException(null,
95              String.format("Unable to cast to expected result type '%s' using expected type '%s'.",
96                  name(),
97                  expectedClass().getName()),
98              ex);
99        }
100     }
101   }
102 
103   /**
104    * Get the Metapath expression identifying the current context node.
105    *
106    * @return the context expression
107    */
108   @NonNull
109   static IMetapathExpression contextNode() {
110     return MetapathExpression.CONTEXT_NODE;
111   }
112 
113   /**
114    * Compile a Metapath expression string.
115    *
116    * @param path
117    *          the metapath expression
118    * @return the compiled expression object
119    * @throws MetapathException
120    *           if an error occurred while compiling the Metapath expression
121    */
122   @NonNull
123   static IMetapathExpression compile(@NonNull String path) {
124     return MetapathExpression.compile(path, StaticContext.instance());
125   }
126 
127   /**
128    * Compiles a Metapath expression string using the provided static context.
129    *
130    * @param path
131    *          the metapath expression
132    * @param staticContext
133    *          the static evaluation context
134    * @return the compiled expression object
135    * @throws MetapathException
136    *           if an error occurred while compiling the Metapath expression
137    */
138   @NonNull
139   static IMetapathExpression compile(@NonNull String path, @NonNull StaticContext staticContext) {
140     return MetapathExpression.compile(path, staticContext);
141   }
142 
143   /**
144    * Gets a new Metapath expression that is compiled on use.
145    * <p>
146    * Lazy compilation may cause additional {@link MetapathException} errors at
147    * evaluation time, since compilation errors are not raised until evaluation.
148    *
149    * @param path
150    *          the metapath expression
151    * @param staticContext
152    *          the static evaluation context
153    * @return the expression object
154    */
155   @NonNull
156   static IMetapathExpression lazyCompile(@NonNull String path, @NonNull StaticContext staticContext) {
157     return new LazyCompilationMetapathExpression(path, staticContext);
158   }
159 
160   /**
161    * Get the original Metapath expression as a string.
162    *
163    * @return the expression
164    */
165   @NonNull
166   String getPath();
167 
168   /**
169    * Get the static context used to compile this Metapath.
170    *
171    * @return the static context
172    */
173   @NonNull
174   StaticContext getStaticContext();
175 
176   /**
177    * Evaluate this Metapath expression without a specific focus. The required
178    * result type will be determined by the {@code resultType} argument.
179    *
180    * @param <T>
181    *          the expected result type
182    * @param resultType
183    *          the type of result to produce
184    * @return the converted result
185    * @throws TypeMetapathException
186    *           if the provided sequence is incompatible with the requested result
187    *           type
188    * @throws MetapathException
189    *           if an error occurred during evaluation
190    * @see ResultType#convert(ISequence)
191    */
192   @Nullable
193   default <T> T evaluateAs(@NonNull ResultType resultType) {
194     return evaluateAs(null, resultType);
195   }
196 
197   /**
198    * Evaluate this Metapath expression using the provided {@code focus} as the
199    * initial evaluation context. The required result type will be determined by
200    * the {@code resultType} argument.
201    *
202    * @param <T>
203    *          the expected result type
204    * @param focus
205    *          the focus of the expression
206    * @param resultType
207    *          the type of result to produce
208    * @return the converted result
209    * @throws TypeMetapathException
210    *           if the provided sequence is incompatible with the requested result
211    *           type
212    * @throws MetapathException
213    *           if an error occurred during evaluation
214    * @see ResultType#convert(ISequence)
215    */
216   @Nullable
217   default <T> T evaluateAs(
218       @Nullable IItem focus,
219       @NonNull ResultType resultType) {
220     ISequence<?> result = evaluate(focus);
221     return resultType.convert(result);
222   }
223 
224   /**
225    * Evaluate this Metapath expression using the provided {@code focus} as the
226    * initial evaluation context. The specific result type will be determined by
227    * the {@code resultType} argument.
228    * <p>
229    * This variant allow for reuse of a provided {@code dynamicContext}.
230    *
231    * @param <T>
232    *          the expected result type
233    * @param focus
234    *          the outer focus of the expression
235    * @param resultType
236    *          the type of result to produce
237    * @param dynamicContext
238    *          the dynamic context to use for evaluation
239    * @return the converted result
240    * @throws TypeMetapathException
241    *           if the provided sequence is incompatible with the requested result
242    *           type
243    * @throws MetapathException
244    *           if an error occurred during evaluation
245    * @see ResultType#convert(ISequence)
246    */
247   @Nullable
248   default <T> T evaluateAs(
249       @Nullable IItem focus,
250       @NonNull ResultType resultType,
251       @NonNull DynamicContext dynamicContext) {
252     ISequence<?> result = evaluate(focus, dynamicContext);
253     return resultType.convert(result);
254   }
255 
256   /**
257    * Evaluate this Metapath expression without a specific focus.
258    *
259    * @param <T>
260    *          the type of items contained in the resulting sequence
261    * @return a sequence of Metapath items representing the result of the
262    *         evaluation
263    * @throws MetapathException
264    *           if an error occurred during evaluation
265    */
266   @NonNull
267   default <T extends IItem> ISequence<T> evaluate() {
268     return evaluate((IItem) null);
269   }
270 
271   /**
272    * Evaluate this Metapath expression using the provided {@code focus} as the
273    * initial evaluation context.
274    *
275    * @param <T>
276    *          the type of items contained in the resulting sequence
277    * @param focus
278    *          the outer focus of the expression
279    * @return a sequence of Metapath items representing the result of the
280    *         evaluation
281    * @throws MetapathException
282    *           if an error occurred during evaluation
283    */
284   @NonNull
285   default <T extends IItem> ISequence<T> evaluate(
286       @Nullable IItem focus) {
287     return evaluate(focus, new DynamicContext(getStaticContext()));
288   }
289 
290   /**
291    * Evaluate this Metapath expression using the provided {@code focus} as the
292    * initial evaluation context.
293    * <p>
294    * This variant allow for reuse of a provided {@code dynamicContext}.
295    *
296    * @param <T>
297    *          the type of items contained in the resulting sequence
298    * @param focus
299    *          the outer focus of the expression
300    * @param dynamicContext
301    *          the dynamic context to use for evaluation
302    * @return a sequence of Metapath items representing the result of the
303    *         evaluation
304    * @throws MetapathException
305    *           if an error occurred during evaluation
306    */
307   @NonNull
308   <T extends IItem> ISequence<T> evaluate(
309       @Nullable IItem focus,
310       @NonNull DynamicContext dynamicContext);
311 }