1 /*
2 * SPDX-FileCopyrightText: none
3 * SPDX-License-Identifier: CC0-1.0
4 */
5
6 package dev.metaschema.core.metapath.function;
7
8 import java.util.Collection;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.stream.Collectors;
14 import java.util.stream.Stream;
15
16 import dev.metaschema.core.metapath.item.IItem;
17 import dev.metaschema.core.metapath.item.ISequence;
18 import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
19 import dev.metaschema.core.metapath.item.atomic.INumericItem;
20 import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
21 import dev.metaschema.core.metapath.type.TypeMetapathException;
22 import dev.metaschema.core.util.ObjectUtils;
23 import edu.umd.cs.findbugs.annotations.NonNull;
24 import edu.umd.cs.findbugs.annotations.Nullable;
25
26 /**
27 * A collection of utility functions for use in implementing Metapath functions.
28 * <p>
29 * This class is thread-safe as all methods are stateless and the internal
30 * constant is immutable.
31 */
32 // FIXME: Remove these methods in favor of direct calls to methods on the item
33 // types
34 public final class FunctionUtils {
35 private FunctionUtils() {
36 // disable
37 }
38
39 /**
40 * Gets the first item of the provided sequence as a {@link INumericItem} value.
41 * If the sequence is empty, then a {@code null} value is returned.
42 *
43 * @param sequence
44 * a Metapath sequence containing the value to convert
45 * @param requireSingleton
46 * if {@code true} then a {@link TypeMetapathException} is thrown if
47 * the sequence contains more than one item
48 * @return the numeric item value, or {@code null} if the result is an empty
49 * sequence
50 * @throws TypeMetapathException
51 * if the sequence contains more than one item, or the item cannot be
52 * cast to a numeric value
53 *
54 */
55 @Deprecated(since = "3.0.0", forRemoval = true)
56 @Nullable
57 public static INumericItem toNumeric(@NonNull ISequence<?> sequence, boolean requireSingleton) {
58 IItem item = sequence.getFirstItem(requireSingleton);
59 return item == null ? null : toNumeric(item);
60 }
61
62 /**
63 * Gets the provided item value as a {@link INumericItem} value.
64 *
65 * @param item
66 * the value to convert
67 * @return the numeric item value
68 * @throws TypeMetapathException
69 * if the sequence contains more than one item, or the item cannot be
70 * cast to a numeric value
71 */
72 @Deprecated(since = "3.0.0", forRemoval = true)
73 @NonNull
74 public static INumericItem toNumeric(@NonNull IItem item) {
75 // atomize
76 IAnyAtomicItem atomicItem = ISequence.getFirstItem(item.atomize(), true);
77 if (atomicItem == null) {
78 throw new InvalidTypeMetapathException(item, "Unable to cast null item");
79 }
80 return toNumeric(atomicItem);
81 }
82
83 /**
84 * Gets the provided item value as a {@link INumericItem} value.
85 *
86 * @param item
87 * the value to convert
88 * @return the numeric item value
89 * @throws TypeMetapathException
90 * if the item cannot be cast to a numeric value
91 */
92 @Deprecated(since = "3.0.0", forRemoval = true)
93 @NonNull
94 public static INumericItem toNumeric(@NonNull IAnyAtomicItem item) {
95 return castToNumeric(item);
96 }
97
98 /**
99 * Gets the provided item value as a {@link INumericItem} value. If the item is
100 * {@code null}, then a {@code null} value is returned.
101 *
102 * @param item
103 * the value to convert
104 * @return the numeric item value
105 * @throws TypeMetapathException
106 * if the item cannot be cast to a numeric value
107 * @deprecated Use {@link #castToNumeric(IAnyAtomicItem)} with null checking
108 * instead
109 */
110 @Deprecated(since = "3.0.0", forRemoval = true)
111 @Nullable
112 public static INumericItem toNumericOrNull(@Nullable IAnyAtomicItem item) {
113 return item == null ? null : castToNumeric(item);
114 }
115
116 /**
117 * Casts the provided item value to a {@link INumericItem} value.
118 * <p>
119 * This method wraps {@link INumericItem#cast(IAnyAtomicItem)} and converts
120 * {@link InvalidValueForCastFunctionException} to
121 * {@link InvalidTypeMetapathException} for consistent exception handling.
122 *
123 * @param item
124 * the value to cast
125 * @return the numeric item value
126 * @throws InvalidTypeMetapathException
127 * if the item cannot be cast to a numeric value
128 */
129 @NonNull
130 public static INumericItem castToNumeric(@NonNull IAnyAtomicItem item) {
131 try {
132 return INumericItem.cast(item);
133 } catch (InvalidValueForCastFunctionException ex) {
134 throw new InvalidTypeMetapathException(item, ex.getLocalizedMessage(), ex);
135 }
136 }
137
138 /**
139 * Casts the provided {@code item} as the result type, if the item is not
140 * {@code null}.
141 *
142 * @param <TYPE>
143 * the Java type to cast to
144 * @param item
145 * the value to cast
146 * @return the item cast to the required type or {@code null} if the item is
147 * {@code null}
148 * @throws ClassCastException
149 * if the item's type is not compatible with the requested type
150 */
151 @SuppressWarnings("unchecked")
152 @Nullable
153 public static <TYPE extends IItem> TYPE asTypeOrNull(@Nullable IItem item) {
154 return (TYPE) item;
155 }
156
157 /**
158 * Casts the provided {@code item} as the result type.
159 *
160 * @param <TYPE>
161 * the Java type to cast to
162 * @param item
163 * the value to cast
164 * @return the item cast to the required type
165 * @throws ClassCastException
166 * if the item's type is not compatible with the requested type
167 */
168 @SuppressWarnings("unchecked")
169 @NonNull
170 public static <TYPE extends IItem> TYPE asType(@NonNull IItem item) {
171 return (TYPE) item;
172 }
173
174 /**
175 * Casts the provided {@code item} as the result sequence type.
176 *
177 * @param <TYPE>
178 * the Java type to cast to
179 * @param sequence
180 * the values to cast
181 * @return the sequence cast to the required type
182 * @throws ClassCastException
183 * if the sequence's type is not compatible with the requested type
184 */
185 @SuppressWarnings("unchecked")
186 @NonNull
187 public static <TYPE extends IItem> ISequence<TYPE> asType(@NonNull ISequence<?> sequence) {
188 return (ISequence<TYPE>) sequence;
189 }
190
191 /**
192 * Casts the provided {@code item} as the result type.
193 *
194 * @param <TYPE>
195 * the Java type to cast to
196 * @param clazz
197 * the Java class instance for the requested type
198 * @param item
199 * the value to cast
200 * @return the item cast to the required type
201 * @throws InvalidTypeMetapathException
202 * if the provided item is {@code null} or if the item's type is not
203 * assignment compatible to the requested type
204 */
205 @Deprecated(since = "3.0.0", forRemoval = true)
206 @NonNull
207 public static <TYPE extends IItem> TYPE requireType(Class<TYPE> clazz, IItem item) {
208 if (item == null) {
209 throw new InvalidTypeMetapathException(
210 null,
211 String.format("Expected non-null type '%s', but the node was null.",
212 clazz.getName()));
213 }
214 if (!clazz.isInstance(item)) {
215 throw new InvalidTypeMetapathException(
216 item,
217 String.format("Expected type '%s', but the node was type '%s'.",
218 clazz.getName(),
219 item.getClass().getName()));
220 }
221 return asType(item);
222 }
223
224 /**
225 * Casts the provided {@code item} as the result type, if the item is not
226 * {@code null}.
227 *
228 * @param <TYPE>
229 * the Java type to cast to
230 * @param clazz
231 * the Java class instance for the requested type
232 * @param item
233 * the value to cast
234 * @return the item cast to the required type or {@code null} if the item is
235 * {@code null}
236 * @throws InvalidTypeMetapathException
237 * if the provided item is {@code null} or if the item's type is not
238 * assignment compatible to the requested type
239 */
240 @Deprecated(since = "3.0.0", forRemoval = true)
241 @Nullable
242 public static <TYPE extends IItem> TYPE requireTypeOrNull(Class<TYPE> clazz, @Nullable IItem item) {
243 if (item == null || clazz.isInstance(item)) {
244 return asTypeOrNull(item);
245 }
246 throw new InvalidTypeMetapathException(
247 item,
248 String.format("Expected type '%s', but the node was type '%s'.",
249 clazz.getName(),
250 item.getClass().getName()));
251 }
252
253 /**
254 * Get a stream of item data types for the stream of items.
255 *
256 * @param items
257 * the Metapath items to get the data types for
258 * @return a stream of data type classes
259 */
260 @Deprecated(since = "3.0.0", forRemoval = true)
261 @NonNull
262 public static Stream<Class<?>> getTypes(@NonNull Stream<? extends IItem> items) {
263 return ObjectUtils.notNull(items.map(Object::getClass));
264 }
265
266 /**
267 * Generate a list of Metapath item Java type classes from a list of Metapath
268 * items.
269 *
270 * @param <T>
271 * the base Java types of the items
272 * @param items
273 * the items to get Java type class for
274 * @return a list of corresponding Java type classes for the provided items
275 */
276 @Deprecated(since = "3.0.0", forRemoval = true)
277 @SuppressWarnings("unchecked")
278 @NonNull
279 public static <T extends IItem> List<Class<? extends T>> getTypes(@NonNull List<T> items) {
280 return ObjectUtils.notNull(items.stream()
281 .map(item -> (Class<? extends T>) item.getClass())
282 .collect(Collectors.toList()));
283 }
284
285 /**
286 * Count the occurrences of the provided data type item {@code classes} used in
287 * the set of provided {@code items}.
288 *
289 * @param <T>
290 * the class type
291 * @param classes
292 * the Metapath item classes to count
293 * @param items
294 * the Metapath items to analyze
295 * @return a mapping of Metapath item class to count
296 */
297 @Deprecated(since = "3.0.0", forRemoval = true)
298 @NonNull
299 public static <T extends IItem> Map<Class<? extends T>, Integer> countTypes(
300 @NonNull Set<Class<? extends T>> classes,
301 @NonNull Collection<? extends T> items) {
302 Map<Class<? extends T>, Integer> retval = new HashMap<>(); // NOPMD
303 for (T item : items) {
304 Class<?> itemClass = item.getClass();
305 for (Class<? extends T> clazz : classes) {
306 if (clazz.isAssignableFrom(itemClass)) {
307 retval.compute(clazz, (cl, current) -> current == null ? 1 : current + 1);
308 }
309 }
310 }
311 return retval;
312 }
313 }