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.LinkedList;
9   import java.util.List;
10  import java.util.function.Predicate;
11  import java.util.stream.Stream;
12  
13  import dev.metaschema.core.metapath.DynamicContext;
14  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
15  import dev.metaschema.core.metapath.item.function.IArrayItem;
16  import dev.metaschema.core.metapath.item.function.IMapItem;
17  import dev.metaschema.core.util.ObjectUtils;
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  /**
22   * A data value that can be a value in a Metapath array or map.
23   */
24  public interface ICollectionValue {
25    /**
26     * Get the collection value as a sequence.
27     * <p>
28     * If the value is already a sequence, the value is returned as a sequence.
29     * Otherwise, if the value is an item, a new sequence will be created containing
30     * only that item.
31     *
32     * @return the resulting sequence
33     */
34    @NonNull
35    default ISequence<?> toSequence() {
36      return this instanceof ISequence
37          // return the sequence
38          ? (ISequence<?>) this
39          // return the item as a new sequence
40          : ISequence.of((IItem) this);
41    }
42  
43    /**
44     * Get the collection value as a sequence.
45     * <p>
46     * If the value is already a sequence, the value is returned as a sequence.
47     * Otherwise, if the value is an item, what is returned depends on the item
48     * type:
49     * <ul>
50     * <li>{@link IArrayItem} or {@link IMapItem}: the contents of the returned
51     * sequence are the items of the array or map. Any member values that are a
52     * sequence are flattened.
53     * <li>Any other item: A singleton sequence is returned containing the item.
54     * </ul>
55     *
56     * @return the resulting sequence
57     */
58    @NonNull
59    ISequence<?> contentsAsSequence();
60  
61    /**
62     * Get the stream of items for the collection value.
63     * <p>
64     * If the collection value is a sequence, then the items in the collection are
65     * returned.
66     *
67     * @param value
68     *          the collection value
69     * @return the sequence of related items
70     */
71    @NonNull
72    static Stream<? extends IItem> normalizeAsItems(@NonNull ICollectionValue value) {
73      return value instanceof IItem
74          ? ObjectUtils.notNull(Stream.of((IItem) value))
75          : value.toSequence().stream();
76    }
77  
78    /**
79     * Produce a stream of atomic items based on the atomic value of these items.
80     * <p>
81     * Supports <a href="https://www.w3.org/TR/xpath-31/#id-atomization">item
82     * atomization</a>.
83     *
84     * @return a stream of atomized atomic items.
85     */
86    @NonNull
87    Stream<IAnyAtomicItem> atomize();
88  
89    /**
90     * Get the stream of items for the collection value.
91     * <p>
92     * If the collection value contains items, then these items are returned.
93     *
94     * @return a stream of related items
95     */
96    @NonNull
97    Stream<? extends IItem> flatten();
98  
99    /**
100    * Determine if this and the other value are deeply equal.
101    * <p>
102    * Item equality is defined by the
103    * <a href="https://www.w3.org/TR/xpath-functions-31/#func-deep-equal">XPath 3.1
104    * fn:deep-equal</a> specification.
105    *
106    * @param other
107    *          the other value to compare to this value to
108    * @param dynamicContext
109    *          used to provide evaluation information, including the implicit
110    *          timezone
111    * @return the {@code true} if the two values are equal, or {@code false}
112    *         otherwise
113    */
114   boolean deepEquals(@Nullable ICollectionValue other, @NonNull DynamicContext dynamicContext);
115 
116   /**
117    * Get a representation of the value based on its type signature.
118    *
119    * @return the signature
120    */
121   @NonNull
122   String toSignature();
123 
124   /**
125    * Provides a {@link Predicate} which filters items in a stream returning
126    * distinct values based on
127    * {@link #deepEquals(ICollectionValue, DynamicContext)}.
128    *
129    * @param dynamicContext
130    *          used to provide evaluation information, including the implicit
131    *          timezone
132    * @return the predicate
133    */
134   static Predicate<? super ICollectionValue> distinctByDeepEquals(@NonNull DynamicContext dynamicContext) {
135     return distinctByDeepEquals(ICollectionValue.class, dynamicContext);
136   }
137 
138   /**
139    * Provides a {@link Predicate} which filters items in a stream returning
140    * distinct values based on
141    * {@link #deepEquals(ICollectionValue, DynamicContext)}.
142    *
143    * @param <T>
144    *          the Java type of the values filtered
145    * @param clazz
146    *          the Java class for the type handled by the predicate
147    * @param dynamicContext
148    *          used to provide evaluation information, including the implicit
149    *          timezone
150    * @return the predicate
151    */
152   static <T extends ICollectionValue> Predicate<? super T> distinctByDeepEquals(
153       @NonNull Class<T> clazz,
154       @NonNull DynamicContext dynamicContext) {
155     List<T> seen = new LinkedList<>();
156     return item -> !seen.stream().anyMatch(other -> item.deepEquals(other, dynamicContext))
157         && seen.add(item); // true
158   }
159 }