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 }