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