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.InvalidTypeMetapathException;
12 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
13 import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
14 import gov.nist.secauto.metaschema.core.metapath.item.IItem;
15 import gov.nist.secauto.metaschema.core.metapath.item.TypeSystem;
16 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
17 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
18 import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
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 @NonNull
95 public static List<ISequence<?>> convertArguments(
96 @NonNull IFunction function,
97 @NonNull List<? extends ISequence<?>> parameters) {
98 @NonNull
99 List<ISequence<?>> retval = new ArrayList<>(parameters.size());
100
101 Iterator<IArgument> argumentIterator = function.getArguments().iterator();
102 IArgument argument = null;
103 for (ISequence<?> parameter : parameters) {
104 if (argumentIterator.hasNext()) {
105 argument = argumentIterator.next();
106 } else if (!function.isArityUnbounded()) {
107 throw new InvalidTypeMetapathException(
108 null,
109 String.format("argument signature doesn't match '%s'", function.toSignature()));
110 }
111
112 assert argument != null;
113 assert parameter != null;
114
115 retval.add(convertArgument(argument, parameter));
116 }
117 return retval;
118 }
119
120 @NonNull
121 private static ISequence<?> convertArgument(
122 @NonNull IArgument argument,
123 @NonNull ISequence<?> parameter) {
124
125 ISequence<?> retval = argument.getSequenceType().getOccurrence().getSequenceHandler().handle(parameter);
126
127
128 if (!retval.isEmpty()) {
129 retval = convertSequence(argument, retval);
130
131
132 Class<? extends IItem> argumentClass = argument.getSequenceType().getType();
133 for (IItem item : retval.getValue()) {
134 Class<? extends IItem> itemClass = item.getClass();
135 if (!argumentClass.isAssignableFrom(itemClass)) {
136 throw new InvalidTypeMetapathException(
137 item,
138 String.format("The type '%s' is not a subtype of '%s'",
139 TypeSystem.getName(itemClass),
140 TypeSystem.getName(argumentClass)));
141 }
142 }
143 }
144 return retval;
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158 @NonNull
159 protected static ISequence<?> convertSequence(@NonNull IArgument argument, @NonNull ISequence<?> sequence) {
160 ISequenceType requiredSequenceType = argument.getSequenceType();
161 Class<? extends IItem> requiredSequenceTypeClass = requiredSequenceType.getType();
162
163 Stream<? extends IItem> stream = sequence.safeStream();
164
165 if (IAnyAtomicItem.class.isAssignableFrom(requiredSequenceTypeClass)) {
166 Stream<? extends IAnyAtomicItem> atomicStream = stream.flatMap(FnData::atomize);
167
168
169
170
171
172 if (IStringItem.class.equals(requiredSequenceTypeClass)) {
173
174 atomicStream = atomicStream.map(item -> IAnyUriItem.class.isInstance(item) ? IStringItem.cast(item) : item);
175 }
176
177 stream = atomicStream;
178 }
179
180 stream = stream.peek(item -> {
181 if (!requiredSequenceTypeClass.isInstance(item)) {
182 throw new InvalidTypeMetapathException(
183 item,
184 String.format("The type '%s' is not a subtype of '%s'",
185 item.getClass().getName(),
186 requiredSequenceTypeClass.getName()));
187 }
188 });
189 assert stream != null;
190
191 return ISequence.of(stream);
192 }
193
194 private IItem getContextItem(@NonNull ISequence<?> focus) {
195 IItem contextItem = null;
196 if (isFocusDepenent()) {
197 contextItem = focus.getFirstItem(true);
198 if (contextItem == null) {
199 throw new DynamicMetapathException(DynamicMetapathException.DYNAMIC_CONTEXT_ABSENT, "The context is empty");
200 }
201 }
202 return contextItem;
203 }
204
205 @Override
206 public ISequence<?> execute(
207 @NonNull List<? extends ISequence<?>> arguments,
208 @NonNull DynamicContext dynamicContext,
209 @NonNull ISequence<?> focus) {
210
211 try {
212 IItem contextItem = getContextItem(focus);
213
214 List<ISequence<?>> convertedArguments = convertArguments(this, arguments);
215
216 CallingContext callingContext = null;
217 ISequence<?> result = null;
218 if (isDeterministic()) {
219
220 callingContext = new CallingContext(convertedArguments, contextItem);
221
222
223 result = dynamicContext.getCachedResult(callingContext);
224 }
225
226 if (result == null) {
227 result = handler.execute(this, convertedArguments, dynamicContext, contextItem);
228
229 if (callingContext != null) {
230
231 dynamicContext.cacheResult(callingContext, result);
232 }
233 }
234
235
236
237
238 return result;
239 } catch (MetapathException ex) {
240 throw new MetapathException(String.format("Unable to execute function '%s'", toSignature()), ex);
241 }
242 }
243
244 @Override
245 public int hashCode() {
246 return Objects.hash(getQName(), getArguments(), handler, properties, result);
247 }
248
249 @Override
250 public boolean equals(Object obj) {
251 if (this == obj) {
252 return true;
253 }
254 if (obj == null || getClass() != obj.getClass()) {
255 return false;
256 }
257 DefaultFunction other = (DefaultFunction) obj;
258 return Objects.equals(getQName(), other.getQName())
259 && Objects.equals(getArguments(), other.getArguments())
260 && Objects.equals(handler, other.handler)
261 && Objects.equals(properties, other.properties)
262 && Objects.equals(result, other.result);
263 }
264
265 @Override
266 public String toString() {
267 return toSignature();
268 }
269
270 public final class CallingContext {
271 @Nullable
272 private final IItem contextItem;
273 @NonNull
274 private final List<ISequence<?>> arguments;
275
276
277
278
279
280
281
282
283
284 private CallingContext(@NonNull List<ISequence<?>> arguments, @Nullable IItem contextItem) {
285 this.contextItem = contextItem;
286 this.arguments = arguments;
287 }
288
289
290
291
292
293
294 @NonNull
295 public DefaultFunction getFunction() {
296 return DefaultFunction.this;
297 }
298
299
300
301
302
303
304 @Nullable
305 public IItem getContextItem() {
306 return contextItem;
307 }
308
309
310
311
312
313
314 @NonNull
315 public List<ISequence<?>> getArguments() {
316 return arguments;
317 }
318
319 @Override
320 public int hashCode() {
321 final int prime = 31;
322 int result = 1;
323 result = prime * result + getFunction().hashCode();
324 return prime * result + Objects.hash(contextItem, arguments);
325 }
326
327 @Override
328 public boolean equals(Object obj) {
329 if (this == obj) {
330 return true;
331 }
332 if (obj == null || getClass() != obj.getClass()) {
333 return false;
334 }
335 CallingContext other = (CallingContext) obj;
336 if (!getFunction().equals(other.getFunction())) {
337 return false;
338 }
339 return Objects.equals(arguments, other.arguments) && Objects.equals(contextItem, other.contextItem);
340 }
341 }
342 }