View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.core.metapath.function;
28  
29  import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
30  import gov.nist.secauto.metaschema.core.metapath.ISequence;
31  import gov.nist.secauto.metaschema.core.metapath.MetapathException;
32  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
33  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
34  
35  import java.net.URI;
36  import java.util.ArrayList;
37  import java.util.EnumSet;
38  import java.util.LinkedList;
39  import java.util.List;
40  import java.util.Objects;
41  import java.util.Set;
42  
43  import javax.xml.namespace.QName;
44  
45  import edu.umd.cs.findbugs.annotations.NonNull;
46  
47  public interface IFunction {
48    enum FunctionProperty {
49      /**
50       * Indicates that the function will produce identical results for the same
51       * arguments (see XPath 3.1 <a href=
52       * "https://www.w3.org/TR/xpath-functions-31/#dt-deterministic">deterministic</a>).
53       * If not assigned to a function definition, a function call with the same
54       * arguments is not guaranteed to produce the same results in the same order for
55       * subsequent calls within the same execution context.
56       */
57      DETERMINISTIC,
58      /**
59       * Indicates that the result of the function depends on property values within
60       * the static or dynamic context and the provided arguments (see XPath 3.1
61       * <a href=
62       * "https://www.w3.org/TR/xpath-functions-31/#dt-context-dependent">context-dependent</a>).
63       * If not assigned to a function definition, a call will not be affected by the
64       * property values within the static or dynamic context and will not have any
65       * arguments.
66       */
67      CONTEXT_DEPENDENT,
68      /**
69       * Indicates that the result of the function depends on the current focus (see
70       * XPath 3.1 <a href=
71       * "https://www.w3.org/TR/xpath-functions-31/#dt-focus-independent">focus-dependent</a>).
72       * If not assigned to a function definition, a call will not be affected by the
73       * current focus.
74       */
75      FOCUS_DEPENDENT,
76      /**
77       * The function allows the last argument to be repeated any number of times.
78       */
79      UNBOUNDED_ARITY;
80    }
81  
82    /**
83     * Retrieve the name of the function.
84     *
85     * @return the function's name
86     */
87    @NonNull
88    String getName();
89  
90    /**
91     * Retrieve the namespace of the function.
92     *
93     * @return the function's namespace
94     */
95    @NonNull
96    String getNamespace();
97  
98    /**
99     * Retrieve the namespace qualified name of the function.
100    *
101    * @return the namespace qualified name
102    */
103   @NonNull
104   default QName getQName() {
105     return new QName(getNamespace(), getName());
106   }
107 
108   /**
109    * Retrieve the set of assigned function properties.
110    *
111    * @return the set of properties or an empty set
112    */
113   @NonNull
114   Set<FunctionProperty> getProperties();
115 
116   /**
117    * Retrieve the list of function arguments.
118    *
119    * @return the function arguments or an empty list if there are none
120    */
121   @NonNull
122   List<IArgument> getArguments();
123 
124   /**
125    * Determine the number of arguments the function has.
126    *
127    * @return the number of function arguments
128    */
129   int arity();
130 
131   /**
132    * Determines if the result of the function call will produce identical results
133    * when provided the same implicit or explicit arguments.
134    *
135    * @return {@code true} if function is deterministic or {@code false} otherwise
136    * @see FunctionProperty#DETERMINISTIC
137    */
138   default boolean isDeterministic() {
139     return getProperties().contains(FunctionProperty.DETERMINISTIC);
140   }
141 
142   /**
143    * Determines if the result of the function call depends on property values
144    * within the static or dynamic context and the provided arguments.
145    *
146    * @return {@code true} if function is context dependent or {@code false}
147    *         otherwise
148    * @see FunctionProperty#CONTEXT_DEPENDENT
149    */
150   default boolean isContextDepenent() {
151     return getProperties().contains(FunctionProperty.CONTEXT_DEPENDENT);
152   }
153 
154   /**
155    * Determines if the result of the function call depends on the current focus.
156    *
157    * @return {@code true} if function is focus dependent or {@code false}
158    *         otherwise
159    * @see FunctionProperty#FOCUS_DEPENDENT
160    */
161   default boolean isFocusDepenent() {
162     return getProperties().contains(FunctionProperty.FOCUS_DEPENDENT);
163   }
164 
165   /**
166    * Determines if the final argument can be repeated.
167    *
168    * @return {@code true} if the final argument can be repeated or {@code false}
169    *         otherwise
170    * @see FunctionProperty#UNBOUNDED_ARITY
171    */
172   default boolean isArityUnbounded() {
173     return getProperties().contains(FunctionProperty.UNBOUNDED_ARITY);
174   }
175 
176   /**
177    * Retrieve the function result sequence type.
178    *
179    * @return the function result sequence type
180    */
181   @NonNull
182   ISequenceType getResult();
183 
184   // /**
185   // * Determines by static analysis if the function supports the expression
186   // arguments provided.
187   // *
188   // * @param arguments
189   // * the expression arguments to evaluate
190   // * @return {@code true} if the arguments are supported or {@code false}
191   // otherwise
192   // */
193   // boolean isSupported(List<IExpression<?>> arguments);
194 
195   @NonNull
196   ISequence<?> execute(
197       @NonNull List<ISequence<?>> arguments,
198       @NonNull DynamicContext dynamicContext,
199       @NonNull ISequence<?> focus) throws MetapathException;
200 
201   /**
202    * Get the signature of the function as a string.
203    *
204    * @return the signature
205    */
206   String toSignature();
207 
208   @NonNull
209   static Builder builder() {
210     return new Builder();
211   }
212 
213   @SuppressWarnings("PMD.LooseCoupling")
214   class Builder {
215     private String name;
216     private String namespace;
217     @SuppressWarnings("null")
218     @NonNull
219     private final EnumSet<FunctionProperty> properties = EnumSet.noneOf(FunctionProperty.class);
220     @NonNull
221     private final List<IArgument> arguments = new LinkedList<>();
222     private Class<? extends IItem> returnType = IItem.class;
223     private Occurrence returnOccurrence = Occurrence.ONE;
224     private IFunctionExecutor functionHandler;
225 
226     @NonNull
227     public Builder name(@NonNull String name) {
228       Objects.requireNonNull(name, "name");
229       if (name.isBlank()) {
230         throw new IllegalArgumentException("the name must be non-blank");
231       }
232       this.name = name.trim();
233       return this;
234     }
235 
236     @NonNull
237     public Builder namespace(@NonNull URI uri) {
238       return namespace(ObjectUtils.notNull(uri.toASCIIString()));
239     }
240 
241     @NonNull
242     public Builder namespace(@NonNull String name) {
243       Objects.requireNonNull(name, "name");
244       if (name.isBlank()) {
245         throw new IllegalArgumentException("the name must be non-blank");
246       }
247       this.namespace = name.trim();
248       return this;
249     }
250 
251     @NonNull
252     public Builder deterministic() {
253       properties.add(FunctionProperty.DETERMINISTIC);
254       return this;
255     }
256 
257     @NonNull
258     public Builder nonDeterministic() {
259       properties.remove(FunctionProperty.DETERMINISTIC);
260       return this;
261     }
262 
263     @NonNull
264     public Builder contextDependent() {
265       properties.add(FunctionProperty.CONTEXT_DEPENDENT);
266       return this;
267     }
268 
269     @NonNull
270     public Builder contextIndependent() {
271       properties.remove(FunctionProperty.CONTEXT_DEPENDENT);
272       return this;
273     }
274 
275     @NonNull
276     public Builder focusDependent() {
277       properties.add(FunctionProperty.FOCUS_DEPENDENT);
278       return this;
279     }
280 
281     @NonNull
282     public Builder focusIndependent() {
283       properties.remove(FunctionProperty.FOCUS_DEPENDENT);
284       return this;
285     }
286 
287     @NonNull
288     public Builder allowUnboundedArity(boolean allow) {
289       if (allow) {
290         properties.add(FunctionProperty.UNBOUNDED_ARITY);
291       } else {
292         properties.remove(FunctionProperty.UNBOUNDED_ARITY);
293       }
294       return this;
295     }
296 
297     @NonNull
298     public Builder returnType(@NonNull Class<? extends IItem> type) {
299       Objects.requireNonNull(type, "type");
300       this.returnType = type;
301       return this;
302     }
303 
304     @NonNull
305     public Builder returnZeroOrOne() {
306       return returnOccurrence(Occurrence.ZERO_OR_ONE);
307     }
308 
309     @NonNull
310     public Builder returnOne() {
311       return returnOccurrence(Occurrence.ONE);
312     }
313 
314     @NonNull
315     public Builder returnZeroOrMore() {
316       return returnOccurrence(Occurrence.ZERO_OR_MORE);
317     }
318 
319     @NonNull
320     public Builder returnOneOrMore() {
321       return returnOccurrence(Occurrence.ONE_OR_MORE);
322     }
323 
324     @NonNull
325     public Builder returnOccurrence(@NonNull Occurrence occurrence) {
326       Objects.requireNonNull(occurrence, "occurrence");
327       this.returnOccurrence = occurrence;
328       return this;
329     }
330 
331     @NonNull
332     public Builder argument(@NonNull IArgument.Builder builder) {
333       return argument(builder.build());
334     }
335 
336     @NonNull
337     public Builder argument(@NonNull IArgument argument) {
338       Objects.requireNonNull(argument, "argument");
339       this.arguments.add(argument);
340       return this;
341     }
342 
343     @NonNull
344     public Builder functionHandler(@NonNull IFunctionExecutor handler) {
345       Objects.requireNonNull(handler, "handler");
346       this.functionHandler = handler;
347       return this;
348     }
349 
350     @NonNull
351     public IFunction build() {
352       ISequenceType sequenceType;
353       if (returnType == null) {
354         sequenceType = ISequenceType.EMPTY;
355       } else {
356         sequenceType = new SequenceTypeImpl(
357             returnType,
358             ObjectUtils.requireNonNull(returnOccurrence, "the return occurrence must not be null"));
359       }
360 
361       if (properties.contains(FunctionProperty.UNBOUNDED_ARITY) && arguments.isEmpty()) {
362         throw new IllegalStateException("to allow unbounded arity, at least one argument must be provided");
363       }
364 
365       return new DefaultFunction(
366           ObjectUtils.requireNonNull(name, "the name must not be null"),
367           ObjectUtils.requireNonNull(namespace, "the namespace must not be null"),
368           properties,
369           new ArrayList<>(arguments),
370           sequenceType,
371           ObjectUtils.requireNonNull(functionHandler, "the function handler must not be null"));
372     }
373   }
374 }