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 }