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