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.DynamicContext;
9   import gov.nist.secauto.metaschema.core.metapath.InvalidTreatTypeDynamicMetapathException;
10  import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentBasedNodeItem;
12  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
13  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
14  import gov.nist.secauto.metaschema.core.metapath.type.InvalidTypeMetapathException;
15  import gov.nist.secauto.metaschema.core.metapath.type.TypeMetapathException;
16  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
17  
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  /**
22   * Provides a variety of utilities for working with Metapath items.
23   */
24  public final class ItemUtils {
25  
26    private ItemUtils() {
27      // disable construction
28    }
29  
30    /**
31     * Checks that the item is an {@link INodeItem}.
32     *
33     * @param dynamicContext
34     *          the dynamic evaluation context
35     * @param item
36     *          the item to check
37     * @return the item cast to a {@link INodeItem}
38     * @throws TypeMetapathException
39     *           if the item is {@code null} or not an {@link INodeItem}
40     */
41    // FIXME: make this a method on the type implementation
42    @NonNull
43    public static INodeItem checkItemIsNodeItem(
44        @NonNull DynamicContext dynamicContext,
45        @Nullable IItem item) {
46      return checkItemIsType(dynamicContext, item, INodeItem.class);
47    }
48  
49    /**
50     * Checks that the item is an {@link IDocumentNodeItem}.
51     *
52     * @param dynamicContext
53     *          the dynamic evaluation context
54     * @param item
55     *          the item to check
56     * @return the item cast to a {@link INodeItem}
57     * @throws TypeMetapathException
58     *           if the item is {@code null} or not an {@link INodeItem}
59     */
60    @NonNull
61    public static IDocumentBasedNodeItem checkItemIsDocumentNodeItem(
62        @NonNull DynamicContext dynamicContext,
63        @Nullable IItem item) {
64      return checkItemIsType(dynamicContext, item, IDocumentBasedNodeItem.class);
65    }
66  
67    @NonNull
68    private static <T extends IItem> T checkItemIsType(
69        @NonNull DynamicContext dynamicContext,
70        @Nullable IItem item,
71        @NonNull Class<T> itemClass) {
72      if (itemClass.isInstance(item)) {
73        return ObjectUtils.notNull(itemClass.cast(item));
74      }
75      if (item == null) {
76        throw new TypeMetapathException(TypeMetapathException.NOT_A_NODE_ITEM_FOR_STEP, "Item is null.")
77            .registerEvaluationContext(dynamicContext);
78      }
79      throw new TypeMetapathException(
80          TypeMetapathException.NOT_A_NODE_ITEM_FOR_STEP,
81          String.format("The item of type '%s' is not of the type '%s'.",
82              item.getClass().getName(),
83              itemClass.getName()))
84                  .registerEvaluationContext(dynamicContext);
85    }
86  
87    /**
88     * Get the ancestor document nodes for the provided items.
89     * <p>
90     * The resulting sequence has items of the {@link IDocumentBasedNodeItem} to
91     * allow for both module and document querying.
92     *
93     * @param dynamicContext
94     *          the dynamic evaluation context
95     * @param items
96     *          the node items to get the document roots for
97     * @return the document root node items
98     */
99    @NonNull
100   public static ISequence<IDocumentBasedNodeItem> getDocumentNodeItems(
101       @NonNull DynamicContext dynamicContext,
102       @NonNull ISequence<?> items) {
103     return ISequence.of(ObjectUtils.notNull(items.stream()
104         // ensures a non-null INodeItem instance
105         .map(item -> checkItemIsNodeItem(dynamicContext, item))
106         .map(item -> Axis.ANCESTOR_OR_SELF.execute(ObjectUtils.notNull(item))
107             .findFirst().stream()
108             .filter(IDocumentBasedNodeItem.class::isInstance)
109             .map(firstItem -> checkItemIsDocumentNodeItem(dynamicContext, firstItem))
110             .findFirst().orElseThrow(() -> new InvalidTreatTypeDynamicMetapathException(
111                 dynamicContext.getExecutionStack(),
112                 String.format("The node '%s' is not the descendant of a document node.",
113                     item.getMetapath()))))));
114   }
115 
116   /**
117    * Check that the item is the type specified by {@code clazz}.
118    *
119    * @param <TYPE>
120    *          the Java type the item is required to match
121    * @param item
122    *          the item to check
123    * @param clazz
124    *          the Java class to check the item against
125    * @return the item cast to the required class value
126    * @throws TypeMetapathException
127    *           if the item is {@code null} or does not match the type specified by
128    *           {@code clazz}
129    */
130   // FIXME: make this a method on the type implementation
131   @SuppressWarnings("unchecked")
132   @NonNull
133   public static <TYPE> TYPE checkItemType(@NonNull IItem item, @NonNull Class<TYPE> clazz) {
134     if (clazz.isInstance(item)) {
135       return (TYPE) item;
136     }
137     throw new InvalidTypeMetapathException(
138         item,
139         String.format(
140             "The item of type '%s' is not the required type '%s'.",
141             item.getClass().getName(),
142             clazz.getName()));
143   }
144 }