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