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