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 }