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