1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.InvalidTypeMetapathException;
32 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
33 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
34 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
35 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
36 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
37 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
38
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.EnumSet;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Objects;
45 import java.util.Set;
46 import java.util.stream.Collectors;
47
48 import edu.umd.cs.findbugs.annotations.NonNull;
49 import edu.umd.cs.findbugs.annotations.Nullable;
50
51
52
53
54 public class DefaultFunction
55 extends AbstractFunction {
56
57
58
59 @NonNull
60 private final Set<FunctionProperty> properties;
61 @NonNull
62 private final ISequenceType result;
63 @NonNull
64 private final IFunctionExecutor handler;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 @SuppressWarnings({ "null", "PMD.LooseCoupling" })
81 DefaultFunction(
82 @NonNull String name,
83 @NonNull String namespace,
84 @NonNull EnumSet<FunctionProperty> properties,
85 @NonNull List<IArgument> arguments,
86 @NonNull ISequenceType result,
87 @NonNull IFunctionExecutor handler) {
88 super(name, namespace, arguments);
89 this.properties = Collections.unmodifiableSet(properties);
90 this.result = result;
91 this.handler = handler;
92 }
93
94 @Override
95 public Set<FunctionProperty> getProperties() {
96 return properties;
97 }
98
99 @Override
100 public ISequenceType getResult() {
101 return result;
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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
241 }
242
243 Class<? extends IItem> argumentClass = argument.getSequenceType().getType();
244
245
246 parameter = convertSequence(argument, parameter);
247
248
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
265
266
267
268
269
270
271
272
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);
291
292
293
294
295
296
297 if (IStringItem.class.equals(requiredSequenceTypeClass) && IAnyUriItem.class.isInstance(item)) {
298 item = IStringItem.cast((IAnyUriItem) item);
299 }
300 }
301
302
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
330 callingContext = new CallingContext(arguments, contextItem);
331
332 result = dynamicContext.getCachedResult(callingContext);
333 }
334
335 if (result == null) {
336
337
338
339
340
341
342
343
344
345
346
347
348
349 result = handler.execute(this, convertedArguments, dynamicContext, contextItem);
350
351 if (callingContext != null) {
352
353 dynamicContext.cacheResult(callingContext, result);
354 }
355 }
356
357
358
359
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;
375 }
376 if (obj == null) {
377 return false;
378 }
379 if (getClass() != obj.getClass()) {
380 return false;
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())
403 .append('(');
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());
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
430
431
432
433
434
435
436 private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) {
437 this.contextItem = contextItem;
438 this.arguments = arguments;
439 }
440
441
442
443
444
445
446 @NonNull
447 public DefaultFunction getFunction() {
448 return DefaultFunction.this;
449 }
450
451
452
453
454
455
456 @Nullable
457 public IItem getContextItem() {
458 return contextItem;
459 }
460
461
462
463
464
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;
484 }
485 if (obj == null) {
486 return false;
487 }
488 if (getClass() != obj.getClass()) {
489 return false;
490 }
491 CallingContext other = (CallingContext) obj;
492 if (!getFunction().equals(other.getFunction())) {
493 return false;
494 }
495 return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem);
496 }
497 }
498 }