1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package gov.nist.secauto.metaschema.core.metapath.function;
7
8 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
10 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
11 import gov.nist.secauto.metaschema.core.metapath.StaticMetapathException;
12 import gov.nist.secauto.metaschema.core.metapath.item.ICollectionValue;
13 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
14 import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
15 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
16 import gov.nist.secauto.metaschema.core.metapath.type.IItemType;
17 import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType;
18 import gov.nist.secauto.metaschema.core.metapath.type.Occurrence;
19 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
20 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
21
22 import java.util.ArrayList;
23 import java.util.EnumSet;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29
30 import edu.umd.cs.findbugs.annotations.NonNull;
31
32 /**
33 * A common interface for all Metapath functions.
34 */
35 public interface IFunction extends IItem {
36 /**
37 * Details specific characteristics of a function.
38 */
39 enum FunctionProperty {
40 /**
41 * Indicates that the function will produce identical results for the same
42 * arguments (see XPath 3.1 <a href=
43 * "https://www.w3.org/TR/xpath-functions-31/#dt-deterministic">deterministic</a>).
44 * If not assigned to a function definition, a function call with the same
45 * arguments is not guaranteed to produce the same results in the same order for
46 * subsequent calls within the same execution context.
47 */
48 DETERMINISTIC,
49 /**
50 * Indicates that the result of the function depends on property values within
51 * the static or dynamic context and the provided arguments (see XPath 3.1
52 * <a href=
53 * "https://www.w3.org/TR/xpath-functions-31/#dt-context-dependent">context-dependent</a>).
54 * If not assigned to a function definition, a call will not be affected by the
55 * property values within the static or dynamic context and will not have any
56 * arguments.
57 */
58 CONTEXT_DEPENDENT,
59 /**
60 * Indicates that the result of the function depends on the current focus (see
61 * XPath 3.1 <a href=
62 * "https://www.w3.org/TR/xpath-functions-31/#dt-focus-independent">focus-dependent</a>).
63 * If not assigned to a function definition, a call will not be affected by the
64 * current focus.
65 */
66 FOCUS_DEPENDENT,
67 /**
68 * The function allows the last argument to be repeated any number of times.
69 */
70 UNBOUNDED_ARITY;
71 }
72
73 /**
74 * Get the type information for this item.
75 *
76 * @return the type information
77 */
78 @NonNull
79 static IItemType type() {
80 return IItemType.function();
81 }
82
83 @Override
84 default IItemType getType() {
85 // TODO: implement this based on the signature
86 return IItemType.function();
87 }
88
89 /**
90 * Retrieve the name of the function.
91 *
92 * @return the function's name
93 */
94 @NonNull
95 default String getName() {
96 return ObjectUtils.notNull(getQName().getLocalName());
97 }
98
99 /**
100 * Retrieve the namespace qualified name of the function.
101 *
102 * @return the namespace qualified name
103 */
104 @NonNull
105 IEnhancedQName getQName();
106
107 /**
108 * Retrieve the set of assigned function properties.
109 *
110 * @return the set of properties or an empty set
111 */
112 @NonNull
113 Set<FunctionProperty> getProperties();
114
115 /**
116 * Retrieve the list of function arguments.
117 *
118 * @return the function arguments or an empty list if there are none
119 */
120 @NonNull
121 List<IArgument> getArguments();
122
123 /**
124 * Determine the number of arguments the function has.
125 *
126 * @return the number of function arguments
127 */
128 int arity();
129
130 /**
131 * Determines if the result of the function call will produce identical results
132 * when provided the same implicit or explicit arguments.
133 *
134 * @return {@code true} if function is deterministic or {@code false} otherwise
135 * @see FunctionProperty#DETERMINISTIC
136 */
137 default boolean isDeterministic() {
138 return getProperties().contains(FunctionProperty.DETERMINISTIC);
139 }
140
141 /**
142 * Determines if the result of the function call depends on property values
143 * within the static or dynamic context and the provided arguments.
144 *
145 * @return {@code true} if function is context dependent or {@code false}
146 * otherwise
147 * @see FunctionProperty#CONTEXT_DEPENDENT
148 */
149 default boolean isContextDepenent() {
150 return getProperties().contains(FunctionProperty.CONTEXT_DEPENDENT);
151 }
152
153 /**
154 * Determines if the result of the function call depends on the current focus.
155 *
156 * @return {@code true} if function is focus dependent or {@code false}
157 * otherwise
158 * @see FunctionProperty#FOCUS_DEPENDENT
159 */
160 default boolean isFocusDependent() {
161 return getProperties().contains(FunctionProperty.FOCUS_DEPENDENT);
162 }
163
164 /**
165 * Determines if the final argument can be repeated.
166 *
167 * @return {@code true} if the final argument can be repeated or {@code false}
168 * otherwise
169 * @see FunctionProperty#UNBOUNDED_ARITY
170 */
171 default boolean isArityUnbounded() {
172 return getProperties().contains(FunctionProperty.UNBOUNDED_ARITY);
173 }
174
175 /**
176 * Retrieve the function result sequence type.
177 *
178 * @return the function result sequence type
179 */
180 @NonNull
181 ISequenceType getResult();
182
183 /**
184 * Determine if the function is a named function.
185 *
186 * @return {@code true} if the function is named or {@code false} if the
187 * function is anonymous
188 */
189 boolean isNamedFunction();
190
191 // /**
192 // * Determines by static analysis if the function supports the expression
193 // arguments provided.
194 // *
195 // * @param arguments
196 // * the expression arguments to evaluate
197 // * @return {@code true} if the arguments are supported or {@code false}
198 // otherwise
199 // */
200 // boolean isSupported(List<IExpression<?>> arguments);
201
202 @Override
203 default boolean deepEquals(ICollectionValue other, DynamicContext dynamicContext) {
204 // this is always the expected result
205 return false;
206 }
207
208 /**
209 * Execute the function with the provided {@code arguments}, using the provided
210 * {@code DynamicContext} and {@code focus}.
211 *
212 * @param arguments
213 * the function arguments or an empty list if there are no arguments
214 * @param dynamicContext
215 * the dynamic evaluation context
216 * @param focus
217 * the current focus or an empty sequence if there is no focus
218 * @return the function result
219 * @throws MetapathException
220 * if an error occurred while executing the function
221 */
222 @NonNull
223 ISequence<?> execute(
224 @NonNull List<? extends ISequence<?>> arguments,
225 @NonNull DynamicContext dynamicContext,
226 @NonNull ISequence<?> focus);
227
228 @Override
229 default IAnyAtomicItem toAtomicItem() {
230 throw new InvalidTypeFunctionException(InvalidTypeFunctionException.DATA_ITEM_IS_FUNCTION, this);
231 }
232
233 /**
234 * Get the signature of the function as a string.
235 *
236 * @return the signature
237 */
238 @Override
239 @NonNull
240 default String toSignature() {
241 return ObjectUtils.notNull(String.format("%s(%s) as %s",
242 getQName(),
243 getArguments().isEmpty() ? ""
244 : getArguments().stream().map(IArgument::toSignature).collect(Collectors.joining(","))
245 + (isArityUnbounded() ? ", ..." : ""),
246 getResult().toSignature()));
247 }
248
249 /**
250 * Construct a new function signature builder.
251 *
252 * @return the new builder instance
253 */
254 @NonNull
255 static Builder builder() {
256 return builder(StaticContext.instance());
257 }
258
259 /**
260 * Construct a new function signature builder.
261 *
262 * @param staticContext
263 * the static context used to lookup data types and function
264 * implementations
265 *
266 * @return the new builder instance
267 */
268 @NonNull
269 static Builder builder(@NonNull StaticContext staticContext) {
270 return new Builder(staticContext);
271 }
272
273 /**
274 * Used to create a function's signature using a builder pattern.
275 */
276 @SuppressWarnings("PMD.LooseCoupling")
277 final class Builder {
278 @NonNull
279 private final StaticContext staticContext;
280 private String name;
281 private String namespace;
282 @SuppressWarnings("null")
283 @NonNull
284 private final EnumSet<FunctionProperty> properties = EnumSet.noneOf(FunctionProperty.class);
285 @NonNull
286 private final List<IArgument> arguments = new LinkedList<>();
287 @NonNull
288 private IItemType returnType = IItem.type();
289 @NonNull
290 private Occurrence returnOccurrence = Occurrence.ONE;
291 private IFunctionExecutor functionHandler;
292
293 private Builder(@NonNull StaticContext staticContext) {
294 this.staticContext = staticContext;
295 }
296
297 private StaticContext getStaticContext() {
298 return staticContext;
299 }
300
301 /**
302 * Define the name of the function.
303 *
304 * @param name
305 * the function's name
306 * @return this builder
307 */
308 @NonNull
309 public Builder name(@NonNull String name) {
310 Objects.requireNonNull(name, "name");
311 if (name.isBlank()) {
312 throw new IllegalArgumentException("the name must be non-blank");
313 }
314 this.name = name.trim();
315 return this;
316 }
317
318 /**
319 * Define the namespace of the function.
320 *
321 * @param name
322 * the function's namespace URI as a string
323 * @return this builder
324 */
325 @NonNull
326 public Builder namespace(@NonNull String name) {
327 Objects.requireNonNull(name, "name");
328 if (name.isBlank()) {
329 throw new IllegalArgumentException("the name must be non-blank");
330 }
331 this.namespace = name.trim();
332 return this;
333 }
334
335 /**
336 * Mark the function as deterministic.
337 *
338 * @return this builder
339 * @see IFunction.FunctionProperty#DETERMINISTIC
340 */
341 @NonNull
342 public Builder deterministic() {
343 properties.add(FunctionProperty.DETERMINISTIC);
344 return this;
345 }
346
347 /**
348 * Mark the function as non-deterministic.
349 *
350 * @return this builder
351 * @see IFunction.FunctionProperty#DETERMINISTIC
352 */
353 @NonNull
354 public Builder nonDeterministic() {
355 properties.remove(FunctionProperty.DETERMINISTIC);
356 return this;
357 }
358
359 /**
360 * Mark the function as context dependent.
361 *
362 * @return this builder
363 * @see IFunction.FunctionProperty#CONTEXT_DEPENDENT
364 */
365 @NonNull
366 public Builder contextDependent() {
367 properties.add(FunctionProperty.CONTEXT_DEPENDENT);
368 return this;
369 }
370
371 /**
372 * Mark the function as context independent.
373 *
374 * @return this builder
375 * @see IFunction.FunctionProperty#CONTEXT_DEPENDENT
376 */
377 @NonNull
378 public Builder contextIndependent() {
379 properties.remove(FunctionProperty.CONTEXT_DEPENDENT);
380 return this;
381 }
382
383 /**
384 * Mark the function as focus dependent.
385 *
386 * @return this builder
387 * @see IFunction.FunctionProperty#FOCUS_DEPENDENT
388 */
389 @NonNull
390 public Builder focusDependent() {
391 properties.add(FunctionProperty.FOCUS_DEPENDENT);
392 return this;
393 }
394
395 /**
396 * Mark the function as focus independent.
397 *
398 * @return this builder
399 * @see IFunction.FunctionProperty#FOCUS_DEPENDENT
400 */
401 @NonNull
402 public Builder focusIndependent() {
403 properties.remove(FunctionProperty.FOCUS_DEPENDENT);
404 return this;
405 }
406
407 /**
408 * Indicate if the last argument can be repeated.
409 *
410 * @param allow
411 * if {@code true} then the the last argument can be repeated an
412 * unlimited number of times, or {@code false} otherwise
413 * @return this builder
414 */
415 @NonNull
416 public Builder allowUnboundedArity(boolean allow) {
417 if (allow) {
418 properties.add(FunctionProperty.UNBOUNDED_ARITY);
419 } else {
420 properties.remove(FunctionProperty.UNBOUNDED_ARITY);
421 }
422 return this;
423 }
424
425 /**
426 * Define the return sequence Java type of the function.
427 *
428 * @param name
429 * the extended qualified name of the function's return data type
430 * @return this builder
431 */
432 @NonNull
433 public Builder returnType(@NonNull String name) {
434 try {
435 this.returnType = getStaticContext().lookupAtomicType(name);
436 } catch (StaticMetapathException ex) {
437 throw new IllegalArgumentException(
438 String.format("No data type with the name '%s'.", name), ex);
439 }
440 return this;
441 }
442
443 /**
444 * Define the return sequence Java type of the function.
445 *
446 * @param name
447 * the qualified name of the function's return data type
448 * @return this builder
449 */
450 @NonNull
451 public Builder returnType(@NonNull IEnhancedQName name) {
452 try {
453 this.returnType = StaticContext.lookupAtomicType(name);
454 } catch (StaticMetapathException ex) {
455 throw new IllegalArgumentException(
456 String.format("No data type with the name '%s'.", name), ex);
457 }
458 return this;
459 }
460
461 /**
462 * Define the return sequence Java type of the function.
463 *
464 * @param type
465 * the function's return Java type
466 * @return this builder
467 */
468 @NonNull
469 public Builder returnType(@NonNull IItemType type) {
470 this.returnType = type;
471 return this;
472 }
473
474 /**
475 * Indicate the sequence returned will contain zero or one items.
476 *
477 * @return this builder
478 */
479 @NonNull
480 public Builder returnZeroOrOne() {
481 return returnOccurrence(Occurrence.ZERO_OR_ONE);
482 }
483
484 /**
485 * Indicate the sequence returned will contain one item.
486 *
487 * @return this builder
488 */
489 @NonNull
490 public Builder returnOne() {
491 return returnOccurrence(Occurrence.ONE);
492 }
493
494 /**
495 * Indicate the sequence returned will contain zero or more items.
496 *
497 * @return this builder
498 */
499 @NonNull
500 public Builder returnZeroOrMore() {
501 return returnOccurrence(Occurrence.ZERO_OR_MORE);
502 }
503
504 /**
505 * Indicate the sequence returned will contain one or more items.
506 *
507 * @return this builder
508 */
509 @NonNull
510 public Builder returnOneOrMore() {
511 return returnOccurrence(Occurrence.ONE_OR_MORE);
512 }
513
514 @NonNull
515 private Builder returnOccurrence(@NonNull Occurrence occurrence) {
516 Objects.requireNonNull(occurrence, "occurrence");
517 this.returnOccurrence = occurrence;
518 return this;
519 }
520
521 /**
522 * Add an argument based on the provided {@code builder}.
523 *
524 * @param builder
525 * the argument builder
526 * @return this builder
527 */
528 @NonNull
529 public Builder argument(@NonNull IArgument.Builder builder) {
530 return argument(builder.build());
531 }
532
533 /**
534 * Add an argument based on the provided {@code argument} signature.
535 *
536 * @param argument
537 * the argument
538 * @return this builder
539 */
540 @NonNull
541 public Builder argument(@NonNull IArgument argument) {
542 Objects.requireNonNull(argument, "argument");
543 this.arguments.add(argument);
544 return this;
545 }
546
547 /**
548 * Specify the static function to call when executing the function.
549 *
550 * @param handler
551 * a method implementing the {@link IFunctionExecutor} functional
552 * interface
553 * @return this builder
554 */
555 @NonNull
556 public Builder functionHandler(@NonNull IFunctionExecutor handler) {
557 Objects.requireNonNull(handler, "handler");
558 this.functionHandler = handler;
559 return this;
560 }
561
562 /**
563 * Builds the function's signature.
564 *
565 * @return the function's signature
566 */
567 @NonNull
568 public IFunction build() {
569 if (properties.contains(FunctionProperty.UNBOUNDED_ARITY) && arguments.isEmpty()) {
570 throw new IllegalStateException("to allow unbounded arity, at least one argument must be provided");
571 }
572
573 return new DefaultFunction(
574 ObjectUtils.requireNonNull(name, "the name must not be null"),
575 ObjectUtils.requireNonNull(namespace, "the namespace must not be null"),
576 properties,
577 new ArrayList<>(arguments),
578 // FIXME: Should return type be ISequenceType?
579 ISequenceType.of(returnType, returnOccurrence),
580 ObjectUtils.requireNonNull(functionHandler, "the function handler must not be null"));
581 }
582 }
583 }