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