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