1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.metapath.item;
7   
8   import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
9   import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException;
10  
11  import java.util.Arrays;
12  import java.util.Objects;
13  import java.util.stream.Stream;
14  
15  import edu.umd.cs.findbugs.annotations.NonNull;
16  import edu.umd.cs.findbugs.annotations.Nullable;
17  
18  /**
19   * Provides a variety of utilities for working with Metapath items.
20   */
21  public final class ItemUtils {
22  
23    private ItemUtils() {
24      // disable construction
25    }
26  
27    /**
28     * Checks that the item is a node item.
29     *
30     * @param item
31     *          the item to check
32     * @return the item cast to a {@link INodeItem}
33     * @throws TypeMetapathException
34     *           if the item is {@code null} or not an {@link INodeItem}
35     */
36    // FIXME: make this a method on the type implementation
37    @NonNull
38    public static INodeItem checkItemIsNodeItemForStep(@Nullable IItem item) {
39      if (item instanceof INodeItem) {
40        return (INodeItem) item;
41      }
42      if (item == null) {
43        throw new TypeMetapathException(TypeMetapathException.NOT_A_NODE_ITEM_FOR_STEP,
44            "Item is null.");
45      }
46      throw new TypeMetapathException(TypeMetapathException.NOT_A_NODE_ITEM_FOR_STEP,
47          String.format(
48              "The item of type '%s' is not a INodeItem.",
49              item.getClass().getName()));
50    }
51  
52    /**
53     * Check that the item is the type specified by {@code clazz}.
54     *
55     * @param <TYPE>
56     *          the Java type the item is required to match
57     * @param item
58     *          the item to check
59     * @param clazz
60     *          the Java class to check the item against
61     * @return the item cast to the required class value
62     * @throws TypeMetapathException
63     *           if the item is {@code null} or does not match the type specified by
64     *           {@code clazz}
65     */
66    // FIXME: make this a method on the type implementation
67    @SuppressWarnings("unchecked")
68    @NonNull
69    public static <TYPE> TYPE checkItemType(@NonNull IItem item, @NonNull Class<TYPE> clazz) {
70      if (clazz.isInstance(item)) {
71        return (TYPE) item;
72      }
73      throw new TypeMetapathException(TypeMetapathException.INVALID_TYPE_ERROR,
74          String.format(
75              "The item of type '%s' is not the required type '%s'.",
76              item.getClass().getName(),
77              clazz.getName()));
78    }
79  
80    public static <T> Stream<Class<? extends T>> interfacesFor(
81        @NonNull Class<? extends T> seed,
82        @NonNull Class<T> base) {
83      return ancestorsOrSelf(seed)
84          .flatMap(clazz -> Stream.ofNullable(asSubclassOrNull(clazz, base)))
85          .flatMap(clazz -> Stream.concat(
86              Stream.of(clazz),
87              Arrays.stream(seed.getInterfaces())
88                  .flatMap(cls -> Stream.ofNullable(asSubclassOrNull(cls, base)))));
89    }
90  
91    private static <T> Stream<Class<? super T>> ancestorsOrSelf(@NonNull Class<T> seed) {
92      return Stream.iterate(seed, Objects::nonNull, Class::getSuperclass);
93    }
94  
95    @Nullable
96    private static <T> Class<? extends T> asSubclassOrNull(Class<?> clazz, Class<T> base) {
97      Class<? extends T> retval = null;
98      try {
99        retval = clazz.asSubclass(base);
100     } catch (@SuppressWarnings("unused") ClassCastException ex) {
101       // not a subclass, do nothing
102     }
103     return retval;
104   }
105 }