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 }