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.DynamicMetapathException;
10 import gov.nist.secauto.metaschema.core.metapath.ISequence;
11 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
12 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
13 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
14 import gov.nist.secauto.metaschema.core.metapath.item.IItemVisitor;
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.metapath.type.IItemType;
19 import gov.nist.secauto.metaschema.core.metapath.type.ISequenceType;
20 import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException;
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.EnumSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.stream.Stream;
30
31 import edu.umd.cs.findbugs.annotations.NonNull;
32 import edu.umd.cs.findbugs.annotations.Nullable;
33
34
35
36
37 public class DefaultFunction
38 extends AbstractFunction {
39
40
41
42 @NonNull
43 private final Set<FunctionProperty> properties;
44 @NonNull
45 private final ISequenceType result;
46 @NonNull
47 private final IFunctionExecutor handler;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 @SuppressWarnings({ "null", "PMD.LooseCoupling" })
64 DefaultFunction(
65 @NonNull String name,
66 @NonNull String namespace,
67 @NonNull EnumSet<FunctionProperty> properties,
68 @NonNull List<IArgument> arguments,
69 @NonNull ISequenceType result,
70 @NonNull IFunctionExecutor handler) {
71 super(name, namespace, arguments);
72 this.properties = Collections.unmodifiableSet(properties);
73 this.result = result;
74 this.handler = handler;
75 }
76
77 @Override
78 public Set<FunctionProperty> getProperties() {
79 return properties;
80 }
81
82 @Override
83 public ISequenceType getResult() {
84 return result;
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98 @NonNull
99 public static List<ISequence<?>> convertArguments(
100 @NonNull IFunction function,
101 @NonNull List<? extends ISequence<?>> parameters,
102 @NonNull DynamicContext dynamicContext) {
103 @NonNull
104 List<ISequence<?>> retval = new ArrayList<>(parameters.size());
105
106 Iterator<IArgument> argumentIterator = function.getArguments().iterator();
107 IArgument argument = null;
108 for (ISequence<?> parameter : parameters) {
109 if (argumentIterator.hasNext()) {
110 argument = argumentIterator.next();
111 } else if (!function.isArityUnbounded()) {
112 throw new InvalidTypeMetapathException(
113 null,
114 String.format("argument signature doesn't match '%s'", function.toSignature()));
115 }
116
117 assert argument != null;
118 assert parameter != null;
119
120 retval.add(convertArgument(argument, parameter, dynamicContext));
121 }
122 return retval;
123 }
124
125 @SuppressWarnings("unused")
126 @NonNull
127 private static ISequence<?> convertArgument(
128 @NonNull IArgument argument,
129 @NonNull ISequence<?> parameter,
130 @NonNull DynamicContext dynamicContext) {
131
132 ISequence<?> retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter);
133
134
135 if (!retval.isEmpty()) {
136 IItemType type = argument.getSequenceType().getType();
137
138 retval = convertSequence(argument, retval, type);
139
140
141 Class<? extends IItem> argumentClass = type.getItemClass();
142 for (IItem item : retval.getValue()) {
143 Class<? extends IItem> itemClass = item.getClass();
144 if (!argumentClass.isAssignableFrom(itemClass)) {
145 throw new InvalidTypeMetapathException(
146 item,
147 String.format("The type '%s' is not a subtype of '%s'",
148 StaticContext.lookupItemType(itemClass),
149 type));
150 }
151 }
152 }
153 return retval;
154 }
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 @NonNull
170 protected static ISequence<?> convertSequence(
171 @NonNull IArgument argument,
172 @NonNull ISequence<?> sequence,
173 @NonNull IItemType requiredSequenceType) {
174 Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getItemClass();
175
176 Stream<? extends IItem> stream = sequence.safeStream();
177
178 if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) {
179 Stream<? extends IAnyAtomicItem> atomicStream = stream.flatMap(IItem::atomize);
180
181
182
183
184
185 if (IStringItem.class.equals(requiredSequenceTypeClass)) {
186
187 atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item);
188 }
189
190 stream = atomicStream;
191 }
192
193 stream = stream.peek(item -> {
194 if (!requiredSequenceTypeClass.isInstance(item)) {
195 throw new InvalidTypeMetapathException(
196 item,
197 String.format("The type '%s' is not a subtype of '%s'",
198 item.getClass().getName(),
199 requiredSequenceTypeClass.getName()));
200 }
201 });
202 assert stream != null;
203
204 return ISequence.of(stream);
205 }
206
207 private IItem getContextItem(@NonNull ISequence<?> focus) {
208 IItem contextItem = null;
209 if (isFocusDepenent()) {
210 contextItem = focus.getFirstItem(true);
211 if (contextItem == null) {
212 throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, "The context is empty");
213 }
214 }
215 return contextItem;
216 }
217
218 @Override
219 public ISequence<?> execute(
220 @NonNull List<? extends ISequence<?>> arguments,
221 @NonNull DynamicContext dynamicContext,
222 @NonNull ISequence<?> focus) {
223
224 try {
225 IItem contextItem = getContextItem(focus);
226
227 List<ISequence<?>> convertedArguments = convertArguments(this, arguments, dynamicContext);
228
229 CallingContext callingContext = null;
230 ISequence<?> result = null;
231 if (isDeterministic()) {
232
233 callingContext = new CallingContext(convertedArguments, contextItem);
234
235
236 result = dynamicContext.getCachedResult(callingContext);
237 }
238
239 if (result == null) {
240 result = handler.execute(this, convertedArguments, dynamicContext, contextItem);
241
242 if (callingContext != null) {
243
244 dynamicContext.cacheResult(callingContext, result);
245 }
246 }
247
248
249
250
251 return result;
252 } catch (MetapathException ex) {
253 throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex);
254 }
255 }
256
257 @Override
258 public int hashCode() {
259 return Objects.hash(getQName(), getArguments(), handler, properties, result);
260 }
261
262 @Override
263 public boolean equals(Object obj) {
264 if (this == obj) {
265 return true;
266 }
267 if (obj == null || getClass() != obj.getClass()) {
268 return false;
269 }
270 DefaultFunction other = (DefaultFunction) obj;
271 return Objects.equals(getQName(), other.getQName())
272 && Objects.equals(getArguments(), other.getArguments())
273 && Objects.equals(handler, other.handler)
274 && Objects.equals(properties, other.properties)
275 && Objects.equals(result, other.result);
276 }
277
278 @Override
279 public String toString() {
280 return toSignature();
281 }
282
283 public final class CallingContext {
284 @Nullable
285 private final IItem contextItem;
286 @NonNull
287 private final List<ISequence<?>> arguments;
288
289
290
291
292
293
294
295
296
297 private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) {
298 this.contextItem = contextItem;
299 this.arguments = arguments;
300 }
301
302
303
304
305
306
307 @NonNull
308 public DefaultFunction getFunction() {
309 return DefaultFunction.this;
310 }
311
312
313
314
315
316
317 @Nullable
318 public IItem getContextItem() {
319 return contextItem;
320 }
321
322
323
324
325
326
327 @NonNull
328 public List<ISequence<?>> getArguments() {
329 return arguments;
330 }
331
332 @Override
333 public int hashCode() {
334 final int prime = 31;
335 int result = 1;
336 result = prime * result + getFunction().hashCode();
337 return prime * result + Objects.hash(contextItem, arguments);
338 }
339
340 @Override
341 public boolean equals(Object obj) {
342 if (this == obj) {
343 return true;
344 }
345 if (obj == null || getClass() != obj.getClass()) {
346 return false;
347 }
348 CallingContext other = (CallingContext) obj;
349 if (!getFunction().equals(other.getFunction())) {
350 return false;
351 }
352 return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem);
353 }
354 }
355
356 @Override
357 public Object getValue() {
358
359 return null;
360 }
361
362 @Override
363 public void accept(IItemVisitor visitor) {
364 visitor.visit(this);
365 }
366 }