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 }