1
2
3
4
5
6 package gov.nist.secauto.metaschema.core.metapath.function;
7
8 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9 import gov.nist.secauto.metaschema.core.metapath.ISequence;
10 import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
11 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
12 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
13 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
14 import gov.nist.secauto.metaschema.core.metapath.item.TypeSystem;
15 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
16 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
17 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
18 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.EnumSet;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.stream.Stream;
28
29 import edu.umd.cs.findbugs.annotations.NonNull;
30 import edu.umd.cs.findbugs.annotations.Nullable;
31
32
33
34
35 public class DefaultFunction
36 extends AbstractFunction {
37
38
39
40 @NonNull
41 private final Set<FunctionProperty> properties;
42 @NonNull
43 private final ISequenceType result;
44 @NonNull
45 private final IFunctionExecutor handler;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 @SuppressWarnings({ "null", "PMD.LooseCoupling" })
62 DefaultFunction(
63 @NonNull String name,
64 @NonNull String namespace,
65 @NonNull EnumSet<FunctionProperty> properties,
66 @NonNull List<IArgument> arguments,
67 @NonNull ISequenceType result,
68 @NonNull IFunctionExecutor handler) {
69 super(name, namespace, arguments);
70 this.properties = Collections.unmodifiableSet(properties);
71 this.result = result;
72 this.handler = handler;
73 }
74
75 @Override
76 public Set<FunctionProperty> getProperties() {
77 return properties;
78 }
79
80 @Override
81 public ISequenceType getResult() {
82 return result;
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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 @NonNull
158 public static List<ISequence<?>> convertArguments(
159 @NonNull IFunction function,
160 @NonNull List<ISequence<?>> parameters) {
161 @NonNull List<ISequence<?>> retval = new ArrayList<>(parameters.size());
162
163 Iterator<IArgument> argumentIterator = function.getArguments().iterator();
164 Iterator<ISequence<?>> parametersIterator = parameters.iterator();
165
166 IArgument argument = null;
167 while (parametersIterator.hasNext()) {
168 if (argumentIterator.hasNext()) {
169 argument = argumentIterator.next();
170 } else if (!function.isArityUnbounded()) {
171 throw new InvalidTypeMetapathException(
172 null,
173 String.format("argument signature doesn't match '%s'", function.toSignature()));
174 }
175
176 ISequence<?> parameter = parametersIterator.next();
177 assert argument != null;
178 assert parameter != null;
179
180 retval.add(convertArgument(argument, parameter));
181 }
182 return retval;
183 }
184
185 @NonNull
186 private static ISequence<?> convertArgument(
187 @NonNull IArgument argument,
188 @NonNull ISequence<?> parameter) {
189
190 ISequence<?> retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter);
191
192
193 if (!retval.isEmpty()) {
194 retval = convertSequence(argument, retval);
195
196
197 Class<? extends IItem> argumentClass = argument.getSequenceType().getType();
198 for (IItem item : retval.getValue()) {
199 Class<? extends IItem> itemClass = item.getClass();
200 if (!argumentClass.isAssignableFrom(itemClass)) {
201 throw new InvalidTypeMetapathException(
202 item,
203 String.format("The type '%s' is not a subtype of '%s'",
204 TypeSystem.getName(itemClass),
205 TypeSystem.getName(argumentClass)));
206 }
207 }
208 }
209 return retval;
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223 @NonNull
224 protected static ISequence<?> convertSequence(@NonNull IArgument argument, @NonNull ISequence<?> sequence) {
225 ISequenceType requiredSequenceType = argument.getSequenceType();
226 Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getType();
227
228 Stream<? extends IItem> stream = sequence.safeStream();
229
230 if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) {
231 Stream<? extends IAnyAtomicItem> atomicStream = stream.flatMap(FnData::atomize);
232
233
234
235
236
237 if (IStringItem.class.equals(requiredSequenceTypeClass)) {
238
239 atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item);
240 }
241
242 stream = atomicStream;
243 }
244
245 stream = stream.peek(item -> {
246 if (!requiredSequenceTypeClass.isInstance(item)) {
247 throw new InvalidTypeMetapathException(
248 item,
249 String.format("The type '%s' is not a subtype of '%s'",
250 item.getClass().getName(),
251 requiredSequenceTypeClass.getName()));
252 }
253 });
254 assert stream != null;
255
256 return ISequence.of(stream);
257 }
258
259 @Override
260 public ISequence<?> execute(
261 @NonNull List<ISequence<?>> arguments,
262 @NonNull DynamicContext dynamicContext,
263 @NonNull ISequence<?> focus) {
264 try {
265 List<ISequence<?>> convertedArguments = convertArguments(this, arguments);
266
267 IItem contextItem = isFocusDepenent() ? ObjectUtils.requireNonNull(focus.getFirstItem(true)) : null;
268
269 CallingContext callingContext = null;
270 ISequence<?> result = null;
271 if (isDeterministic()) {
272
273 callingContext = new CallingContext(arguments, contextItem);
274
275
276 result = dynamicContext.getCachedResult(callingContext);
277 }
278
279 if (result == null) {
280 result = handler.execute(this, convertedArguments, dynamicContext, contextItem);
281
282 if (callingContext != null) {
283
284 dynamicContext.cacheResult(callingContext, result);
285 }
286 }
287
288
289
290
291 return result;
292 } catch (MetapathException ex) {
293 throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex);
294 }
295 }
296
297 @Override
298 public int hashCode() {
299 return Objects.hash(getQName(), getArguments(), handler, properties, result);
300 }
301
302 @Override
303 public boolean equals(Object obj) {
304 if (this == obj) {
305 return true;
306 }
307 if (obj == null) {
308 return false;
309 }
310 if (getClass() != obj.getClass()) {
311 return false;
312 }
313 DefaultFunction other = (DefaultFunction) obj;
314 return Objects.equals(getQName(), other.getQName())
315 && Objects.equals(getArguments(), other.getArguments())
316 && Objects.equals(handler, other.handler)
317 && Objects.equals(properties, other.properties)
318 && Objects.equals(result, other.result);
319 }
320
321 @Override
322 public String toString() {
323 return toSignature();
324 }
325
326 public final class CallingContext {
327 @Nullable
328 private final IItem contextItem;
329 @NonNull
330 private final List<ISequence<?>> arguments;
331
332
333
334
335
336
337
338
339
340 private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) {
341 this.contextItem = contextItem;
342 this.arguments = arguments;
343 }
344
345
346
347
348
349
350 @NonNull
351 public DefaultFunction getFunction() {
352 return DefaultFunction.this;
353 }
354
355
356
357
358
359
360 @Nullable
361 public IItem getContextItem() {
362 return contextItem;
363 }
364
365
366
367
368
369
370 @NonNull
371 public List<ISequence<?>> getArguments() {
372 return arguments;
373 }
374
375 @Override
376 public int hashCode() {
377 final int prime = 31;
378 int result = 1;
379 result = prime * result + getFunction().hashCode();
380 result = prime * result + Objects.hash(contextItem, arguments);
381 return result;
382 }
383
384 @Override
385 public boolean equals(Object obj) {
386 if (this == obj) {
387 return true;
388 }
389 if (obj == null) {
390 return false;
391 }
392 if (getClass() != obj.getClass()) {
393 return false;
394 }
395 CallingContext other = (CallingContext) obj;
396 if (!getFunction().equals(other.getFunction())) {
397 return false;
398 }
399 return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem);
400 }
401 }
402 }