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 }