1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package dev.metaschema.core.metapath.item;
7
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.function.Function;
15 import java.util.stream.Collectors;
16 import java.util.stream.Stream;
17
18 import dev.metaschema.core.metapath.impl.AbstractSequence;
19 import dev.metaschema.core.metapath.impl.SequenceN;
20 import dev.metaschema.core.metapath.impl.SingletonSequence;
21 import dev.metaschema.core.metapath.impl.StreamSequence;
22 import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
23 import dev.metaschema.core.metapath.item.function.IArrayItem;
24 import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
25 import dev.metaschema.core.metapath.type.TypeMetapathException;
26 import dev.metaschema.core.util.CustomCollectors;
27 import dev.metaschema.core.util.ObjectUtils;
28 import edu.umd.cs.findbugs.annotations.NonNull;
29 import edu.umd.cs.findbugs.annotations.Nullable;
30
31 /**
32 * Represents an ordered collection of Metapath expression results.
33 * <p>
34 * Items is a sequence are typically ordered based on their position in the
35 * original node graph based on a depth first ordering.
36 *
37 * @param <ITEM>
38 * the Java type of the items in a sequence
39 */
40 public interface ISequence<ITEM extends IItem> extends List<ITEM>, ICollectionValue {
41 /**
42 * Get an empty sequence.
43 *
44 * @param <T>
45 * the item type
46 * @return the empty sequence
47 */
48 @SuppressWarnings("null")
49 @NonNull
50 static <T extends IItem> ISequence<T> empty() {
51 return AbstractSequence.empty();
52 }
53
54 /**
55 * Ensure the sequence is able to be iterated over multiple times.
56 * <p>
57 * This method can be used to ensure that the sequence can be streamed or
58 * iterated over multiple times. Implementations should make sure that when this
59 * method is called, the resulting stream is supported by an underlying list.
60 *
61 * @return a sequence with the same contents, which may be the same sequence
62 */
63 @NonNull
64 default ISequence<ITEM> reusable() {
65 return this;
66 }
67
68 /**
69 * Get a stream guaranteed to be backed by a list.
70 * <p>
71 * This call ensures that the sequence is backed by a {@link List} and not a
72 * {@link Stream}, so the underlying collection can be reused. This is done by
73 * first calling {@link #reusable()}.
74 *
75 * @return the stream
76 */
77 @NonNull
78 default Stream<ITEM> safeStream() {
79 return ObjectUtils.notNull(reusable().stream());
80 }
81
82 /**
83 * Get the items in this sequence as a {@link Stream}.
84 *
85 * @return a stream containing all the items of the sequence
86 */
87 @Override
88 @NonNull
89 Stream<ITEM> stream();
90
91 /**
92 * Retrieves the first item in a sequence.
93 * <p>
94 * If the sequence is empty, a {@code null} result is returned. If
95 * requireSingleton is {@code true} and the sequence contains more than one
96 * item, a {@link TypeMetapathException} is thrown.
97 *
98 * @param <T>
99 * the item type to return derived from the provided sequence
100 * @param items
101 * the sequence to retrieve the first item from
102 * @param requireSingleton
103 * if {@code true} then a {@link TypeMetapathException} is thrown if
104 * the sequence contains more than one item
105 * @return {@code null} if the sequence is empty, or the item otherwise
106 * @throws TypeMetapathException
107 * if the sequence contains more than one item and requireSingleton is
108 * {@code true}
109 */
110 static <T extends IItem> T getFirstItem(@NonNull ISequence<T> items, boolean requireSingleton) {
111 return getFirstItem(items.safeStream(), requireSingleton);
112 }
113
114 /**
115 * Retrieves the first item in a stream of items.
116 * <p>
117 * If the sequence is empty, a {@code null} result is returned. If
118 * requireSingleton is {@code true} and the sequence contains more than one
119 * item, a {@link TypeMetapathException} is thrown.
120 *
121 * @param <T>
122 * the item type to return derived from the provided sequence
123 * @param items
124 * the sequence to retrieve the first item from
125 * @param requireSingleton
126 * if {@code true} then a {@link TypeMetapathException} is thrown if
127 * the sequence contains more than one item
128 * @return {@code null} if the sequence is empty, or the item otherwise
129 * @throws TypeMetapathException
130 * if the sequence contains more than one item and requireSingleton is
131 * {@code true}
132 */
133 static <T extends IItem> T getFirstItem(@NonNull Stream<T> items, boolean requireSingleton) {
134 return items.limit(2)
135 .reduce((t, u) -> {
136 if (requireSingleton) {
137 throw new InvalidTypeMetapathException(
138 null,
139 String.format("sequence expected to contain only one item, but found multiple"));
140 }
141 return t;
142 }).orElse(null);
143 }
144
145 /**
146 * Retrieves the first item in this sequence.
147 * <p>
148 * If the sequence is empty, a {@code null} result is returned. If
149 * requireSingleton is {@code true} and the sequence contains more than one
150 * item, a {@link TypeMetapathException} is thrown.
151 *
152 * @param requireSingleton
153 * if {@code true} then a {@link TypeMetapathException} is thrown if
154 * the sequence contains more than one item
155 * @return {@code null} if the sequence is empty, or the item otherwise
156 * @throws TypeMetapathException
157 * if the sequence contains more than one item and requireSingleton is
158 * {@code true}
159 */
160 // FIXME: 3.0: Consider changing this to use the Java Optional
161 @Nullable
162 default ITEM getFirstItem(boolean requireSingleton) {
163 return getFirstItem(this, requireSingleton);
164 }
165
166 /**
167 * An implementation of XPath 3.1
168 * <a href="https://www.w3.org/TR/xpath-functions-31/#func-data">fn:data</a>
169 * supporting <a href="https://www.w3.org/TR/xpath-31/#id-atomization">item
170 * atomization</a>.
171 *
172 * @return the atomized result
173 */
174 @Override
175 @NonNull
176 default Stream<IAnyAtomicItem> atomize() {
177 return ObjectUtils.notNull(stream().flatMap(IItem::atomize));
178 }
179
180 /**
181 * Get this sequence as a collection value.
182 *
183 * @return the collection value
184 */
185 @NonNull
186 default ICollectionValue toCollectionValue() {
187 ICollectionValue retval;
188 switch (size()) {
189 case 0:
190 retval = empty();
191 break;
192 case 1:
193 // get the singleton item
194 retval = ObjectUtils.notNull(stream().findFirst().get());
195 break;
196 default:
197 // get this sequence of 2 or more items
198 retval = this;
199 }
200 return retval;
201 }
202
203 @Override
204 default Stream<? extends IItem> flatten() {
205 // TODO: Is a safe stream needed here?
206 return safeStream();
207 }
208
209 /**
210 * Get this sequence.
211 *
212 * @return this sequence
213 */
214 @Override
215 default ISequence<ITEM> toSequence() {
216 return this;
217 }
218
219 @Override
220 default ISequence<?> contentsAsSequence() {
221 return this;
222 }
223
224 /**
225 * Apply the provided {@code mapFunction} to each item in the sequence.
226 *
227 * @param <T>
228 * the Java type of the provided items
229 * @param <R>
230 * the Java type of the resulting items
231 * @param mapFunction
232 * the map function to apply to each item in the provided sequence
233 * @param seq
234 * the sequence of items to map
235 * @return a new sequence containing the mapped items
236 */
237 static <T extends R, R extends IItem> ISequence<R> map(
238 @NonNull Function<T, R> mapFunction,
239 @NonNull ISequence<T> seq) {
240 return seq.safeStream()
241 .map(mapFunction::apply)
242 .collect(CustomCollectors.toSequence());
243 }
244
245 /**
246 * Returns an unmodifiable sequence containing the provided {@code items}.
247 * <p>
248 * If the provided collection is already an {@link ISequence}, it is returned
249 * unchanged. Otherwise, the collection is wrapped directly without making a
250 * defensive copy. If you need a sequence that is independent of the original
251 * collection, use {@link #copyOf(Collection)} instead.
252 *
253 * @param <ITEM_TYPE>
254 * the type of items contained in the sequence.
255 * @param items
256 * the items to add to the sequence
257 * @return the new sequence, or the same sequence if items is already an
258 * {@link ISequence}
259 */
260 @NonNull
261 static <ITEM_TYPE extends IItem> ISequence<ITEM_TYPE> ofCollection(
262 @NonNull Collection<ITEM_TYPE> items) {
263 ISequence<ITEM_TYPE> retval;
264 if (items instanceof ISequence) {
265 retval = (ISequence<ITEM_TYPE>) items;
266 } else if (items.isEmpty()) {
267 retval = empty();
268 } else if (items.size() == 1) {
269 retval = new SingletonSequence<>(ObjectUtils.notNull(items.iterator().next()));
270 } else {
271 retval = new SequenceN<>(items);
272 }
273 return retval;
274 }
275
276 /**
277 * Returns an unmodifiable sequence containing the provided {@code item}.
278 * <p>
279 * If the item is {@code null} and empty sequence will be created.
280 *
281 * @param <T>
282 * the type of items contained in the sequence.
283 * @param item
284 * the item to add to the sequence
285 * @return the new sequence
286 */
287 @NonNull
288 static <T extends IItem> ISequence<T> of(
289 @Nullable T item) {
290 return item == null ? empty() : new SingletonSequence<>(item);
291 }
292
293 /**
294 * Returns an unmodifiable sequence containing the provided {@code items}.
295 *
296 * @param <T>
297 * the type of items contained in the sequence.
298 * @param items
299 * the items to add to the sequence
300 * @return the new sequence
301 */
302 // TODO: remove null check on callers
303 @NonNull
304 static <T extends IItem> ISequence<T> of(@NonNull Stream<T> items) {
305 return new StreamSequence<>(items);
306 }
307
308 /**
309 * Returns an unmodifiable sequence containing zero elements.
310 *
311 * @param <T>
312 * the item type
313 * @return an empty {@code ISequence}
314 */
315 @NonNull
316 static <T extends IItem> ISequence<T> of() {
317 return empty();
318 }
319
320 /**
321 * Returns an unmodifiable sequence containing two items.
322 *
323 * @param <T>
324 * the {@code ISequence}'s item type
325 * @param e1
326 * the first item
327 * @param e2
328 * the second item
329 * @return an {@code ISequence} containing the specified items
330 * @throws NullPointerException
331 * if an item is {@code null}
332 */
333 @NonNull
334 static <T extends IItem> ISequence<T> of(T e1, T e2) {
335 return new SequenceN<>(e1, e2);
336 }
337
338 /**
339 * Returns an unmodifiable sequence containing three elements.
340 *
341 * @param <T>
342 * the {@code ISequence}'s item type
343 * @param e1
344 * the first item
345 * @param e2
346 * the second item
347 * @param e3
348 * the third item
349 * @return an {@code ISequence} containing the specified items
350 * @throws NullPointerException
351 * if an item is {@code null}
352 */
353 @NonNull
354 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3) {
355 return new SequenceN<>(e1, e2, e3);
356 }
357
358 /**
359 * Returns an unmodifiable sequence containing four items.
360 *
361 * @param <T>
362 * the {@code ISequence}'s item type
363 * @param e1
364 * the first item
365 * @param e2
366 * the second item
367 * @param e3
368 * the third item
369 * @param e4
370 * the fourth item
371 * @return an {@code ISequence} containing the specified items
372 * @throws NullPointerException
373 * if an item is {@code null}
374 */
375 @NonNull
376 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4) {
377 return new SequenceN<>(e1, e2, e3, e4);
378 }
379
380 /**
381 * Returns an unmodifiable sequence containing five items.
382 *
383 * @param <T>
384 * the {@code ISequence}'s item type
385 * @param e1
386 * the first item
387 * @param e2
388 * the second item
389 * @param e3
390 * the third item
391 * @param e4
392 * the fourth item
393 * @param e5
394 * the fifth item
395 * @return an {@code ISequence} containing the specified items
396 * @throws NullPointerException
397 * if an item is {@code null}
398 */
399 @NonNull
400 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5) {
401 return new SequenceN<>(e1, e2, e3, e4, e5);
402 }
403
404 /**
405 * Returns an unmodifiable sequence containing six items.
406 *
407 * @param <T>
408 * the {@code ISequence}'s item type
409 * @param e1
410 * the first item
411 * @param e2
412 * the second item
413 * @param e3
414 * the third item
415 * @param e4
416 * the fourth item
417 * @param e5
418 * the fifth item
419 * @param e6
420 * the sixth item
421 * @return an {@code ISequence} containing the specified items
422 * @throws NullPointerException
423 * if an item is {@code null}
424 */
425 @NonNull
426 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5, T e6) {
427 return new SequenceN<>(e1, e2, e3, e4, e5, e6);
428 }
429
430 /**
431 * Returns an unmodifiable sequence containing seven items.
432 *
433 * @param <T>
434 * the {@code ISequence}'s item type
435 * @param e1
436 * the first item
437 * @param e2
438 * the second item
439 * @param e3
440 * the third item
441 * @param e4
442 * the fourth item
443 * @param e5
444 * the fifth item
445 * @param e6
446 * the sixth item
447 * @param e7
448 * the seventh item
449 * @return an {@code ISequence} containing the specified items
450 * @throws NullPointerException
451 * if an item is {@code null}
452 */
453 @NonNull
454 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5, T e6, T e7) {
455 return new SequenceN<>(e1, e2, e3, e4, e5, e6, e7);
456 }
457
458 /**
459 * Returns an unmodifiable sequence containing eight items.
460 *
461 * @param <T>
462 * the {@code ISequence}'s item type
463 * @param e1
464 * the first item
465 * @param e2
466 * the second item
467 * @param e3
468 * the third item
469 * @param e4
470 * the fourth item
471 * @param e5
472 * the fifth item
473 * @param e6
474 * the sixth item
475 * @param e7
476 * the seventh item
477 * @param e8
478 * the eighth item
479 * @return an {@code ISequence} containing the specified items
480 * @throws NullPointerException
481 * if an item is {@code null}
482 */
483 @NonNull
484 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8) {
485 return new SequenceN<>(e1, e2, e3, e4, e5, e6, e7, e8);
486 }
487
488 /**
489 * Returns an unmodifiable sequence containing nine items.
490 *
491 * @param <T>
492 * the {@code ISequence}'s item type
493 * @param e1
494 * the first item
495 * @param e2
496 * the second item
497 * @param e3
498 * the third item
499 * @param e4
500 * the fourth item
501 * @param e5
502 * the fifth item
503 * @param e6
504 * the sixth item
505 * @param e7
506 * the seventh item
507 * @param e8
508 * the eighth item
509 * @param e9
510 * the ninth item
511 * @return an {@code ISequence} containing the specified items
512 * @throws NullPointerException
513 * if an item is {@code null}
514 */
515 @NonNull
516 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8, T e9) {
517 return new SequenceN<>(e1, e2, e3, e4, e5, e6, e7, e8, e9);
518 }
519
520 /**
521 * Returns an unmodifiable sequence containing ten items.
522 *
523 * @param <T>
524 * the {@code ISequence}'s item type
525 * @param e1
526 * the first item
527 * @param e2
528 * the second item
529 * @param e3
530 * the third item
531 * @param e4
532 * the fourth item
533 * @param e5
534 * the fifth item
535 * @param e6
536 * the sixth item
537 * @param e7
538 * the seventh item
539 * @param e8
540 * the eighth item
541 * @param e9
542 * the ninth item
543 * @param e10
544 * the tenth item
545 * @return an {@code IArrayItem} containing the specified items
546 * @throws NullPointerException
547 * if an item is {@code null}
548 */
549 @NonNull
550 static <T extends IItem> ISequence<T> of(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8, T e9, T e10) {
551 return new SequenceN<>(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
552 }
553
554 /**
555 * Returns an unmodifiable sequence containing an arbitrary number of items.
556 *
557 * @param <T>
558 * the {@code ISequence}'s item type
559 * @param items
560 * the items to be contained in the list
561 * @return an {@code ISequence} containing the specified items
562 * @throws NullPointerException
563 * if an item is {@code null} or if the array is {@code null}
564 */
565 @SafeVarargs
566 @NonNull
567 static <T extends IItem> ISequence<T> of(@NonNull T... items) {
568 return items.length == 0 ? empty() : new SequenceN<>(items);
569 }
570
571 /**
572 * Returns an unmodifiable sequence containing the items of the given
573 * Collection, in its iteration order. The given Collection must not be null,
574 * and it must not contain any null items. If the given Collection is
575 * subsequently modified, the returned array item will not reflect such
576 * modifications.
577 *
578 * @param <T>
579 * the {@code ISequence}'s item type
580 * @param collection
581 * a {@code Collection} from which items are drawn, must be non-null
582 * @return an {@code ISequence} containing the items of the given
583 * {@code Collection}
584 * @throws NullPointerException
585 * if collection is null, or if it contains any nulls
586 * @since 10
587 */
588 @SuppressWarnings("unchecked")
589 @NonNull
590 static <T extends IItem> ISequence<T> copyOf(Collection<? extends T> collection) {
591 return collection instanceof IArrayItem
592 ? (ISequence<T>) collection
593 : collection.isEmpty()
594 ? empty()
595 : new SequenceN<>(new ArrayList<>(collection));
596 }
597
598 /**
599 * Count the occurrences of items in this sequence that are instances of the
600 * provided type classes.
601 * <p>
602 * For each class in the provided set, this method counts how many items in the
603 * sequence are assignable to that class.
604 *
605 * @param <T>
606 * the base type of the classes to count
607 * @param classes
608 * the set of classes to count occurrences for
609 * @return a map from each class to the count of matching items
610 */
611 @NonNull
612 default <T extends IItem> Map<Class<? extends T>, Integer> countTypes(
613 @NonNull Set<Class<? extends T>> classes) {
614 Map<Class<? extends T>, Integer> retval = new HashMap<>();
615 for (ITEM item : this) {
616 Class<?> itemClass = item.getClass();
617 for (Class<? extends T> clazz : classes) {
618 if (clazz.isAssignableFrom(itemClass)) {
619 retval.compute(clazz, (cl, current) -> current == null ? 1 : current + 1);
620 }
621 }
622 }
623 return retval;
624 }
625
626 /**
627 * Get a list of the Java class types for each item in this sequence.
628 *
629 * @return a list of class types corresponding to each item in the sequence
630 */
631 @SuppressWarnings("unchecked")
632 @NonNull
633 default List<Class<? extends ITEM>> getItemTypes() {
634 return ObjectUtils.notNull(safeStream()
635 .map(item -> (Class<? extends ITEM>) item.getClass())
636 .collect(Collectors.toList()));
637 }
638 }