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.InvalidTypeMetapathException; 032import gov.nist.secauto.metaschema.core.metapath.MetapathException; 033import gov.nist.secauto.metaschema.core.metapath.function.library.FnData; 034import gov.nist.secauto.metaschema.core.metapath.item.IItem; 035import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem; 036import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem; 037import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem; 038 039import java.util.ArrayList; 040import java.util.Collections; 041import java.util.EnumSet; 042import java.util.Iterator; 043import java.util.List; 044import java.util.Objects; 045import java.util.Set; 046import java.util.stream.Collectors; 047 048import edu.umd.cs.findbugs.annotations.NonNull; 049import edu.umd.cs.findbugs.annotations.Nullable; 050 051/** 052 * Provides a concrete implementation of a function call executor. 053 */ 054public class DefaultFunction 055 extends AbstractFunction { 056 // private static final Logger logger = 057 // LogManager.getLogger(AbstractFunction.class); 058 059 @NonNull 060 private final Set<FunctionProperty> properties; 061 @NonNull 062 private final ISequenceType result; 063 @NonNull 064 private final IFunctionExecutor handler; 065 066 /** 067 * Construct a new function signature. 068 * 069 * @param name 070 * the name of the function 071 * @param properties 072 * the characteristics of the function 073 * @param arguments 074 * the argument signatures or an empty list 075 * @param result 076 * the type of the result 077 * @param handler 078 * the handler to call to execute the function 079 */ 080 @SuppressWarnings({ "null", "PMD.LooseCoupling" }) 081 DefaultFunction( 082 @NonNull String name, 083 @NonNull String namespace, 084 @NonNull EnumSet<FunctionProperty> properties, 085 @NonNull List<IArgument> arguments, 086 @NonNull ISequenceType result, 087 @NonNull IFunctionExecutor handler) { 088 super(name, namespace, arguments); 089 this.properties = Collections.unmodifiableSet(properties); 090 this.result = result; 091 this.handler = handler; 092 } 093 094 @Override 095 public Set<FunctionProperty> getProperties() { 096 return properties; 097 } 098 099 @Override 100 public ISequenceType getResult() { 101 return result; 102 } 103 // 104 // @Override 105 // public boolean isSupported(List<IExpression<?>> expressionArguments) { 106 // boolean retval; 107 // if (expressionArguments.isEmpty() && getArguments().isEmpty()) { 108 // // no arguments 109 // retval = true; 110 // // } else if (arity() == 1 && expressionArguments.isEmpty()) { 111 // // // the context item will be the argument 112 // // // TODO: check the context item for type compatibility 113 // // retval = true; 114 // } else if ((expressionArguments.size() == getArguments().size()) 115 // || (isArityUnbounded() && expressionArguments.size() > 116 // getArguments().size())) { 117 // retval = true; 118 // // check that argument requirements are satisfied 119 // Iterator<IArgument> argumentIterator = getArguments().iterator(); 120 // Iterator<IExpression<?>> expressionIterator = expressionArguments.iterator(); 121 // 122 // IArgument argument = null; 123 // while (argumentIterator.hasNext()) { 124 // argument = argumentIterator.next(); 125 // IExpression<?> expression = expressionIterator.hasNext() ? 126 // expressionIterator.next() : null; 127 // 128 // if (expression != null) { 129 // // is the expression supported by the argument? 130 // retval = argument.isSupported(expression); 131 // if (!retval) { 132 // break; 133 // } 134 // } else { 135 // // there are no more expression arguments. Make sure that the remaining 136 // arguments are optional 137 // if (!argument.getSequenceType().getOccurrence().isOptional()) { 138 // retval = false; 139 // break; 140 // } 141 // } 142 // } 143 // 144 // if (retval && expressionIterator.hasNext()) { 145 // if (isArityUnbounded()) { 146 // // check remaining expressions against the last argument 147 // while (expressionIterator.hasNext()) { 148 // IExpression<?> expression = expressionIterator.next(); 149 // @SuppressWarnings("null") 150 // boolean result = argument.isSupported(expression); 151 // if (!result) { 152 // retval = result; 153 // break; 154 // } 155 // } 156 // } else { 157 // // there are extra expressions, which do not match the arguments 158 // retval = false; 159 // } 160 // } 161 // } else { 162 // retval = false; 163 // } 164 // return retval; 165 // } 166 167 /** 168 * Converts arguments in an attempt to align with the function's signature. 169 * 170 * @param function 171 * the function 172 * @param parameters 173 * the argument parameters 174 * @return the converted argument list 175 */ 176 @NonNull 177 public static List<ISequence<?>> convertArguments( 178 @NonNull IFunction function, 179 @NonNull List<ISequence<?>> parameters) { 180 @NonNull List<ISequence<?>> retval = new ArrayList<>(parameters.size()); 181 182 Iterator<IArgument> argumentIterator = function.getArguments().iterator(); 183 Iterator<ISequence<?>> parametersIterator = parameters.iterator(); 184 185 IArgument argument = null; 186 while (parametersIterator.hasNext()) { 187 if (argumentIterator.hasNext()) { 188 argument = argumentIterator.next(); 189 } else if (!function.isArityUnbounded()) { 190 throw new InvalidTypeMetapathException( 191 null, 192 String.format("argument signature doesn't match '%s'", function.toSignature())); 193 } 194 195 assert argument != null; 196 197 ISequence<?> parameter = parametersIterator.next(); 198 199 int size = parameter.size(); 200 Occurrence occurrence = argument.getSequenceType().getOccurrence(); 201 switch (occurrence) { 202 case ONE: { 203 if (size != 1) { 204 throw new InvalidTypeMetapathException( 205 null, 206 String.format("a sequence of one expected, but found '%d'", size)); 207 } 208 209 IItem item = FunctionUtils.getFirstItem(parameter, true); 210 parameter = item == null ? ISequence.empty() : ISequence.of(item); 211 break; 212 } 213 case ZERO_OR_ONE: { 214 if (size > 1) { 215 throw new InvalidTypeMetapathException( 216 null, 217 String.format("a sequence of zero or one expected, but found '%d'", size)); 218 } 219 220 IItem item = FunctionUtils.getFirstItem(parameter, false); 221 parameter = item == null ? ISequence.empty() : ISequence.of(item); 222 break; 223 } 224 case ONE_OR_MORE: 225 if (size < 1) { 226 throw new InvalidTypeMetapathException( 227 null, 228 String.format("a sequence of zero or more expected, but found '%d'", size)); 229 } 230 break; 231 case ZERO: 232 if (size != 0) { 233 throw new InvalidTypeMetapathException( 234 null, 235 String.format("an empty sequence expected, but found '%d'", size)); 236 } 237 break; 238 case ZERO_OR_MORE: 239 default: 240 // do nothing 241 } 242 243 Class<? extends IItem> argumentClass = argument.getSequenceType().getType(); 244 245 // apply function conversion and type promotion to the parameter 246 parameter = convertSequence(argument, parameter); 247 248 // check resulting values 249 for (IItem item : parameter.asList()) { 250 Class<? extends IItem> itemClass = item.getClass(); 251 if (!argumentClass.isAssignableFrom(itemClass)) { 252 throw new InvalidTypeMetapathException( 253 item, 254 String.format("The type '%s' is not a subtype of '%s'", itemClass.getName(), argumentClass.getName())); 255 } 256 } 257 258 retval.add(parameter); 259 } 260 return retval; 261 } 262 263 /** 264 * Based on XPath 3.1 265 * <a href="https://www.w3.org/TR/xpath-31/#dt-function-conversion">function 266 * conversion</a> rules. 267 * 268 * @param argument 269 * the function argument signature details 270 * @param sequence 271 * the sequence to convert 272 * @return the converted sequence 273 */ 274 @NonNull 275 protected static ISequence<?> convertSequence(@NonNull IArgument argument, @NonNull ISequence<?> sequence) { 276 @NonNull ISequence<?> retval; 277 if (sequence.isEmpty()) { 278 retval = ISequence.empty(); 279 } else { 280 ISequenceType requiredSequenceType = argument.getSequenceType(); 281 Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getType(); 282 283 List<IItem> result = new ArrayList<>(sequence.size()); 284 285 boolean atomize = IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass); 286 287 for (IItem item : sequence.asList()) { 288 assert item != null; 289 if (atomize) { 290 item = FnData.fnDataItem(item); // NOPMD - intentional 291 292 // if (IUntypedAtomicItem.class.isInstance(item)) { // NOPMD 293 // // TODO: apply cast to atomic type 294 // } 295 296 // promote URIs to strings if a string is required 297 if (IStringItem.class.equals(requiredSequenceTypeClass) && IAnyUriItem.class.isInstance(item)) { 298 item = IStringItem.cast((IAnyUriItem) item); // NOPMD - intentional 299 } 300 } 301 302 // item = requiredSequenceType. 303 if (!requiredSequenceTypeClass.isInstance(item)) { 304 throw new InvalidTypeMetapathException( 305 item, 306 String.format("The type '%s' is not a subtype of '%s'", item.getClass().getName(), 307 requiredSequenceTypeClass.getName())); 308 } 309 result.add(item); 310 } 311 retval = ISequence.of(result); 312 } 313 return retval; 314 } 315 316 @Override 317 public ISequence<?> execute( 318 @NonNull List<ISequence<?>> arguments, 319 @NonNull DynamicContext dynamicContext, 320 @NonNull ISequence<?> focus) { 321 try { 322 List<ISequence<?>> convertedArguments = convertArguments(this, arguments); 323 324 IItem contextItem = isFocusDepenent() ? FunctionUtils.requireFirstItem(focus, true) : null; 325 326 CallingContext callingContext = null; 327 ISequence<?> result = null; 328 if (isDeterministic()) { 329 // check cache 330 callingContext = new CallingContext(arguments, contextItem); 331 // attempt to get the result from the cache 332 result = dynamicContext.getCachedResult(callingContext); 333 } 334 335 if (result == null) { 336 // logger.info(String.format("Executing function '%s' with arguments '%s'.", 337 // toSignature(), 338 // convertedArguments.toString())); 339 340 // INodeItem actualFocus = focus == null ? null : focus.getNodeItem(); 341 // if (isFocusDepenent() && actualFocus == null) { 342 // throw new 343 // DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, 344 // "Null 345 // focus"); 346 // } 347 // result = handler.execute(this, convertedArguments, dynamicContext, 348 // actualFocus); 349 result = handler.execute(this, convertedArguments, dynamicContext, contextItem); 350 351 if (callingContext != null) { 352 // add result to cache 353 dynamicContext.cacheResult(callingContext, result); 354 } 355 } 356 357 // logger.info(String.format("Executed function '%s' with arguments '%s' 358 // producing result '%s'", 359 // toSignature(), convertedArguments.toString(), result.asList().toString())); 360 return result; 361 } catch (MetapathException ex) { 362 throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex); 363 } 364 } 365 366 @Override 367 public int hashCode() { 368 return Objects.hash(getName(), getNamespace(), getArguments(), handler, properties, result); 369 } 370 371 @Override 372 public boolean equals(Object obj) { 373 if (this == obj) { 374 return true; // NOPMD - readability 375 } 376 if (obj == null) { 377 return false; // NOPMD - readability 378 } 379 if (getClass() != obj.getClass()) { 380 return false; // NOPMD - readability 381 } 382 DefaultFunction other = (DefaultFunction) obj; 383 return Objects.equals(getName(), other.getName()) 384 && Objects.equals(getNamespace(), other.getNamespace()) 385 && Objects.equals(getArguments(), other.getArguments()) 386 && Objects.equals(handler, other.handler) 387 && Objects.equals(properties, other.properties) 388 && Objects.equals(result, other.result); 389 } 390 391 @Override 392 public String toString() { 393 return toSignature(); 394 } 395 396 @Override 397 public String toSignature() { 398 StringBuilder builder = new StringBuilder() 399 .append("Q{") 400 .append(getNamespace()) 401 .append('}') 402 .append(getName()) // name 403 .append('('); // arguments 404 405 List<IArgument> arguments = getArguments(); 406 if (arguments.isEmpty()) { 407 builder.append("()"); 408 } else { 409 builder.append(arguments.stream().map(argument -> argument.toSignature()).collect(Collectors.joining(","))); 410 411 if (isArityUnbounded()) { 412 builder.append(", ..."); 413 } 414 } 415 416 builder.append(") as ") 417 .append(getResult().toSignature());// return type 418 419 return builder.toString(); 420 } 421 422 public final class CallingContext { 423 @Nullable 424 private final IItem contextItem; 425 @NonNull 426 private final List<ISequence<?>> arguments; 427 428 /** 429 * Set up the execution context for this function. 430 * 431 * @param arguments 432 * the function arguments 433 * @param contextItem 434 * the current node context 435 */ 436 private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) { 437 this.contextItem = contextItem; 438 this.arguments = arguments; 439 } 440 441 /** 442 * Get the function instance associated with the calling context. 443 * 444 * @return the function instance 445 */ 446 @NonNull 447 public DefaultFunction getFunction() { 448 return DefaultFunction.this; 449 } 450 451 /** 452 * Get the node item focus associated with the calling context. 453 * 454 * @return the function instance 455 */ 456 @Nullable 457 public IItem getContextItem() { 458 return contextItem; 459 } 460 461 /** 462 * Get the arguments associated with the calling context. 463 * 464 * @return the arguments 465 */ 466 @NonNull 467 public List<ISequence<?>> getArguments() { 468 return arguments; 469 } 470 471 @Override 472 public int hashCode() { 473 final int prime = 31; 474 int result = 1; 475 result = prime * result + getFunction().hashCode(); 476 result = prime * result + Objects.hash(contextItem, arguments); 477 return result; 478 } 479 480 @Override 481 public boolean equals(Object obj) { 482 if (this == obj) { 483 return true; // NOPMD - readability 484 } 485 if (obj == null) { 486 return false; // NOPMD - readability 487 } 488 if (getClass() != obj.getClass()) { 489 return false; // NOPMD - readability 490 } 491 CallingContext other = (CallingContext) obj; 492 if (!getFunction().equals(other.getFunction())) { 493 return false; // NOPMD - readability 494 } 495 return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem); 496 } 497 } 498}