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