DefaultFunction.java

  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. package gov.nist.secauto.metaschema.core.metapath.function;

  27. import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
  28. import gov.nist.secauto.metaschema.core.metapath.ISequence;
  29. import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
  30. import gov.nist.secauto.metaschema.core.metapath.MetapathException;
  31. import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
  32. import gov.nist.secauto.metaschema.core.metapath.item.IItem;
  33. import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
  34. import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
  35. import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;

  36. import java.util.ArrayList;
  37. import java.util.Collections;
  38. import java.util.EnumSet;
  39. import java.util.Iterator;
  40. import java.util.List;
  41. import java.util.Objects;
  42. import java.util.Set;
  43. import java.util.stream.Collectors;

  44. import edu.umd.cs.findbugs.annotations.NonNull;
  45. import edu.umd.cs.findbugs.annotations.Nullable;

  46. /**
  47.  * Provides a concrete implementation of a function call executor.
  48.  */
  49. public class DefaultFunction
  50.     extends AbstractFunction {
  51.   // private static final Logger logger =
  52.   // LogManager.getLogger(AbstractFunction.class);

  53.   @NonNull
  54.   private final Set<FunctionProperty> properties;
  55.   @NonNull
  56.   private final ISequenceType result;
  57.   @NonNull
  58.   private final IFunctionExecutor handler;

  59.   /**
  60.    * Construct a new function signature.
  61.    *
  62.    * @param name
  63.    *          the name of the function
  64.    * @param properties
  65.    *          the characteristics of the function
  66.    * @param arguments
  67.    *          the argument signatures or an empty list
  68.    * @param result
  69.    *          the type of the result
  70.    * @param handler
  71.    *          the handler to call to execute the function
  72.    */
  73.   @SuppressWarnings({ "null", "PMD.LooseCoupling" })
  74.   DefaultFunction(
  75.       @NonNull String name,
  76.       @NonNull String namespace,
  77.       @NonNull EnumSet<FunctionProperty> properties,
  78.       @NonNull List<IArgument> arguments,
  79.       @NonNull ISequenceType result,
  80.       @NonNull IFunctionExecutor handler) {
  81.     super(name, namespace, arguments);
  82.     this.properties = Collections.unmodifiableSet(properties);
  83.     this.result = result;
  84.     this.handler = handler;
  85.   }

  86.   @Override
  87.   public Set<FunctionProperty> getProperties() {
  88.     return properties;
  89.   }

  90.   @Override
  91.   public ISequenceType getResult() {
  92.     return result;
  93.   }
  94.   //
  95.   // @Override
  96.   // public boolean isSupported(List<IExpression<?>> expressionArguments) {
  97.   // boolean retval;
  98.   // if (expressionArguments.isEmpty() && getArguments().isEmpty()) {
  99.   // // no arguments
  100.   // retval = true;
  101.   // // } else if (arity() == 1 && expressionArguments.isEmpty()) {
  102.   // // // the context item will be the argument
  103.   // // // TODO: check the context item for type compatibility
  104.   // // retval = true;
  105.   // } else if ((expressionArguments.size() == getArguments().size())
  106.   // || (isArityUnbounded() && expressionArguments.size() >
  107.   // getArguments().size())) {
  108.   // retval = true;
  109.   // // check that argument requirements are satisfied
  110.   // Iterator<IArgument> argumentIterator = getArguments().iterator();
  111.   // Iterator<IExpression<?>> expressionIterator = expressionArguments.iterator();
  112.   //
  113.   // IArgument argument = null;
  114.   // while (argumentIterator.hasNext()) {
  115.   // argument = argumentIterator.next();
  116.   // IExpression<?> expression = expressionIterator.hasNext() ?
  117.   // expressionIterator.next() : null;
  118.   //
  119.   // if (expression != null) {
  120.   // // is the expression supported by the argument?
  121.   // retval = argument.isSupported(expression);
  122.   // if (!retval) {
  123.   // break;
  124.   // }
  125.   // } else {
  126.   // // there are no more expression arguments. Make sure that the remaining
  127.   // arguments are optional
  128.   // if (!argument.getSequenceType().getOccurrence().isOptional()) {
  129.   // retval = false;
  130.   // break;
  131.   // }
  132.   // }
  133.   // }
  134.   //
  135.   // if (retval && expressionIterator.hasNext()) {
  136.   // if (isArityUnbounded()) {
  137.   // // check remaining expressions against the last argument
  138.   // while (expressionIterator.hasNext()) {
  139.   // IExpression<?> expression = expressionIterator.next();
  140.   // @SuppressWarnings("null")
  141.   // boolean result = argument.isSupported(expression);
  142.   // if (!result) {
  143.   // retval = result;
  144.   // break;
  145.   // }
  146.   // }
  147.   // } else {
  148.   // // there are extra expressions, which do not match the arguments
  149.   // retval = false;
  150.   // }
  151.   // }
  152.   // } else {
  153.   // retval = false;
  154.   // }
  155.   // return retval;
  156.   // }

  157.   /**
  158.    * Converts arguments in an attempt to align with the function's signature.
  159.    *
  160.    * @param function
  161.    *          the function
  162.    * @param parameters
  163.    *          the argument parameters
  164.    * @return the converted argument list
  165.    */
  166.   @NonNull
  167.   public static List<ISequence<?>> convertArguments(
  168.       @NonNull IFunction function,
  169.       @NonNull List<ISequence<?>> parameters) {
  170.     @NonNull List<ISequence<?>> retval = new ArrayList<>(parameters.size());

  171.     Iterator<IArgument> argumentIterator = function.getArguments().iterator();
  172.     Iterator<ISequence<?>> parametersIterator = parameters.iterator();

  173.     IArgument argument = null;
  174.     while (parametersIterator.hasNext()) {
  175.       if (argumentIterator.hasNext()) {
  176.         argument = argumentIterator.next();
  177.       } else if (!function.isArityUnbounded()) {
  178.         throw new InvalidTypeMetapathException(
  179.             null,
  180.             String.format("argument signature doesn't match '%s'", function.toSignature()));
  181.       }

  182.       assert argument != null;

  183.       ISequence<?> parameter = parametersIterator.next();

  184.       int size = parameter.size();
  185.       Occurrence occurrence = argument.getSequenceType().getOccurrence();
  186.       switch (occurrence) {
  187.       case ONE: {
  188.         if (size != 1) {
  189.           throw new InvalidTypeMetapathException(
  190.               null,
  191.               String.format("a sequence of one expected, but found '%d'", size));
  192.         }

  193.         IItem item = FunctionUtils.getFirstItem(parameter, true);
  194.         parameter = item == null ? ISequence.empty() : ISequence.of(item);
  195.         break;
  196.       }
  197.       case ZERO_OR_ONE: {
  198.         if (size > 1) {
  199.           throw new InvalidTypeMetapathException(
  200.               null,
  201.               String.format("a sequence of zero or one expected, but found '%d'", size));
  202.         }

  203.         IItem item = FunctionUtils.getFirstItem(parameter, false);
  204.         parameter = item == null ? ISequence.empty() : ISequence.of(item);
  205.         break;
  206.       }
  207.       case ONE_OR_MORE:
  208.         if (size < 1) {
  209.           throw new InvalidTypeMetapathException(
  210.               null,
  211.               String.format("a sequence of zero or more expected, but found '%d'", size));
  212.         }
  213.         break;
  214.       case ZERO:
  215.         if (size != 0) {
  216.           throw new InvalidTypeMetapathException(
  217.               null,
  218.               String.format("an empty sequence expected, but found '%d'", size));
  219.         }
  220.         break;
  221.       case ZERO_OR_MORE:
  222.       default:
  223.         // do nothing
  224.       }

  225.       Class<? extends IItem> argumentClass = argument.getSequenceType().getType();

  226.       // apply function conversion and type promotion to the parameter
  227.       parameter = convertSequence(argument, parameter);

  228.       // check resulting values
  229.       for (IItem item : parameter.asList()) {
  230.         Class<? extends IItem> itemClass = item.getClass();
  231.         if (!argumentClass.isAssignableFrom(itemClass)) {
  232.           throw new InvalidTypeMetapathException(
  233.               item,
  234.               String.format("The type '%s' is not a subtype of '%s'", itemClass.getName(), argumentClass.getName()));
  235.         }
  236.       }

  237.       retval.add(parameter);
  238.     }
  239.     return retval;
  240.   }

  241.   /**
  242.    * Based on XPath 3.1
  243.    * <a href="https://www.w3.org/TR/xpath-31/#dt-function-conversion">function
  244.    * conversion</a> rules.
  245.    *
  246.    * @param argument
  247.    *          the function argument signature details
  248.    * @param sequence
  249.    *          the sequence to convert
  250.    * @return the converted sequence
  251.    */
  252.   @NonNull
  253.   protected static ISequence<?> convertSequence(@NonNull IArgument argument, @NonNull ISequence<?> sequence) {
  254.     @NonNull ISequence<?> retval;
  255.     if (sequence.isEmpty()) {
  256.       retval = ISequence.empty();
  257.     } else {
  258.       ISequenceType requiredSequenceType = argument.getSequenceType();
  259.       Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getType();

  260.       List<IItem> result = new ArrayList<>(sequence.size());

  261.       boolean atomize = IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass);

  262.       for (IItem item : sequence.asList()) {
  263.         assert item != null;
  264.         if (atomize) {
  265.           item = FnData.fnDataItem(item); // NOPMD - intentional

  266.           // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD
  267.           // // TODO: apply cast to atomic type
  268.           // }

  269.           // promote URIs to strings if a string is required
  270.           if (IStringItem.class.equals(requiredSequenceTypeClass) && IAnyUriItem.class.isInstance(item)) {
  271.             item = IStringItem.cast((IAnyUriItem) item); // NOPMD - intentional
  272.           }
  273.         }

  274.         // item = requiredSequenceType.
  275.         if (!requiredSequenceTypeClass.isInstance(item)) {
  276.           throw new InvalidTypeMetapathException(
  277.               item,
  278.               String.format("The type '%s' is not a subtype of '%s'", item.getClass().getName(),
  279.                   requiredSequenceTypeClass.getName()));
  280.         }
  281.         result.add(item);
  282.       }
  283.       retval = ISequence.of(result);
  284.     }
  285.     return retval;
  286.   }

  287.   @Override
  288.   public ISequence<?> execute(
  289.       @NonNull List<ISequence<?>> arguments,
  290.       @NonNull DynamicContext dynamicContext,
  291.       @NonNull ISequence<?> focus) {
  292.     try {
  293.       List<ISequence<?>> convertedArguments = convertArguments(this, arguments);

  294.       IItem contextItem = isFocusDepenent() ? FunctionUtils.requireFirstItem(focus, true) : null;

  295.       CallingContext callingContext = null;
  296.       ISequence<?> result = null;
  297.       if (isDeterministic()) {
  298.         // check cache
  299.         callingContext = new CallingContext(arguments, contextItem);
  300.         // attempt to get the result from the cache
  301.         result = dynamicContext.getCachedResult(callingContext);
  302.       }

  303.       if (result == null) {
  304.         // logger.info(String.format("Executing function '%s' with arguments '%s'.",
  305.         // toSignature(),
  306.         // convertedArguments.toString()));

  307.         // INodeItem actualFocus = focus == null ? null : focus.getNodeItem();
  308.         // if (isFocusDepenent() && actualFocus == null) {
  309.         // throw new
  310.         // DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT,
  311.         // "Null
  312.         // focus");
  313.         // }
  314.         // result = handler.execute(this, convertedArguments, dynamicContext,
  315.         // actualFocus);
  316.         result = handler.execute(this, convertedArguments, dynamicContext, contextItem);

  317.         if (callingContext != null) {
  318.           // add result to cache
  319.           dynamicContext.cacheResult(callingContext, result);
  320.         }
  321.       }

  322.       // logger.info(String.format("Executed function '%s' with arguments '%s'
  323.       // producing result '%s'",
  324.       // toSignature(), convertedArguments.toString(), result.asList().toString()));
  325.       return result;
  326.     } catch (MetapathException ex) {
  327.       throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex);
  328.     }
  329.   }

  330.   @Override
  331.   public int hashCode() {
  332.     return Objects.hash(getName(), getNamespace(), getArguments(), handler, properties, result);
  333.   }

  334.   @Override
  335.   public boolean equals(Object obj) {
  336.     if (this == obj) {
  337.       return true; // NOPMD - readability
  338.     }
  339.     if (obj == null) {
  340.       return false; // NOPMD - readability
  341.     }
  342.     if (getClass() != obj.getClass()) {
  343.       return false; // NOPMD - readability
  344.     }
  345.     DefaultFunction other = (DefaultFunction) obj;
  346.     return Objects.equals(getName(), other.getName())
  347.         && Objects.equals(getNamespace(), other.getNamespace())
  348.         && Objects.equals(getArguments(), other.getArguments())
  349.         && Objects.equals(handler, other.handler)
  350.         && Objects.equals(properties, other.properties)
  351.         && Objects.equals(result, other.result);
  352.   }

  353.   @Override
  354.   public String toString() {
  355.     return toSignature();
  356.   }

  357.   @Override
  358.   public String toSignature() {
  359.     StringBuilder builder = new StringBuilder()
  360.         .append("Q{")
  361.         .append(getNamespace())
  362.         .append('}')
  363.         .append(getName()) // name
  364.         .append('('); // arguments

  365.     List<IArgument> arguments = getArguments();
  366.     if (arguments.isEmpty()) {
  367.       builder.append("()");
  368.     } else {
  369.       builder.append(arguments.stream().map(argument -> argument.toSignature()).collect(Collectors.joining(",")));

  370.       if (isArityUnbounded()) {
  371.         builder.append(", ...");
  372.       }
  373.     }

  374.     builder.append(") as ")
  375.         .append(getResult().toSignature());// return type

  376.     return builder.toString();
  377.   }

  378.   public final class CallingContext {
  379.     @Nullable
  380.     private final IItem contextItem;
  381.     @NonNull
  382.     private final List<ISequence<?>> arguments;

  383.     /**
  384.      * Set up the execution context for this function.
  385.      *
  386.      * @param arguments
  387.      *          the function arguments
  388.      * @param contextItem
  389.      *          the current node context
  390.      */
  391.     private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) {
  392.       this.contextItem = contextItem;
  393.       this.arguments = arguments;
  394.     }

  395.     /**
  396.      * Get the function instance associated with the calling context.
  397.      *
  398.      * @return the function instance
  399.      */
  400.     @NonNull
  401.     public DefaultFunction getFunction() {
  402.       return DefaultFunction.this;
  403.     }

  404.     /**
  405.      * Get the node item focus associated with the calling context.
  406.      *
  407.      * @return the function instance
  408.      */
  409.     @Nullable
  410.     public IItem getContextItem() {
  411.       return contextItem;
  412.     }

  413.     /**
  414.      * Get the arguments associated with the calling context.
  415.      *
  416.      * @return the arguments
  417.      */
  418.     @NonNull
  419.     public List<ISequence<?>> getArguments() {
  420.       return arguments;
  421.     }

  422.     @Override
  423.     public int hashCode() {
  424.       final int prime = 31;
  425.       int result = 1;
  426.       result = prime * result + getFunction().hashCode();
  427.       result = prime * result + Objects.hash(contextItem, arguments);
  428.       return result;
  429.     }

  430.     @Override
  431.     public boolean equals(Object obj) {
  432.       if (this == obj) {
  433.         return true; // NOPMD - readability
  434.       }
  435.       if (obj == null) {
  436.         return false; // NOPMD - readability
  437.       }
  438.       if (getClass() != obj.getClass()) {
  439.         return false; // NOPMD - readability
  440.       }
  441.       CallingContext other = (CallingContext) obj;
  442.       if (!getFunction().equals(other.getFunction())) {
  443.         return false; // NOPMD - readability
  444.       }
  445.       return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem);
  446.     }
  447.   }
  448. }