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 }