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