001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.metapath.function;
028
029import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
030import gov.nist.secauto.metaschema.core.metapath.ISequence;
031import gov.nist.secauto.metaschema.core.metapath.MetapathException;
032import gov.nist.secauto.metaschema.core.metapath.item.IItem;
033import gov.nist.secauto.metaschema.core.util.ObjectUtils;
034
035import java.net.URI;
036import java.util.ArrayList;
037import java.util.EnumSet;
038import java.util.LinkedList;
039import java.util.List;
040import java.util.Objects;
041import java.util.Set;
042
043import javax.xml.namespace.QName;
044
045import edu.umd.cs.findbugs.annotations.NonNull;
046
047public interface IFunction {
048  enum FunctionProperty {
049    /**
050     * Indicates that the function will produce identical results for the same
051     * arguments (see XPath 3.1 <a href=
052     * "https://www.w3.org/TR/xpath-functions-31/#dt-deterministic">deterministic</a>).
053     * If not assigned to a function definition, a function call with the same
054     * arguments is not guaranteed to produce the same results in the same order for
055     * subsequent calls within the same execution context.
056     */
057    DETERMINISTIC,
058    /**
059     * Indicates that the result of the function depends on property values within
060     * the static or dynamic context and the provided arguments (see XPath 3.1
061     * <a href=
062     * "https://www.w3.org/TR/xpath-functions-31/#dt-context-dependent">context-dependent</a>).
063     * If not assigned to a function definition, a call will not be affected by the
064     * property values within the static or dynamic context and will not have any
065     * arguments.
066     */
067    CONTEXT_DEPENDENT,
068    /**
069     * Indicates that the result of the function depends on the current focus (see
070     * XPath 3.1 <a href=
071     * "https://www.w3.org/TR/xpath-functions-31/#dt-focus-independent">focus-dependent</a>).
072     * If not assigned to a function definition, a call will not be affected by the
073     * current focus.
074     */
075    FOCUS_DEPENDENT,
076    /**
077     * The function allows the last argument to be repeated any number of times.
078     */
079    UNBOUNDED_ARITY;
080  }
081
082  /**
083   * Retrieve the name of the function.
084   *
085   * @return the function's name
086   */
087  @NonNull
088  String getName();
089
090  /**
091   * Retrieve the namespace of the function.
092   *
093   * @return the function's namespace
094   */
095  @NonNull
096  String getNamespace();
097
098  /**
099   * 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}