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