1   
2   package dev.metaschema.core.metapath.item.node;
3   
4   import java.net.URI;
5   import java.util.Collection;
6   import java.util.List;
7   import java.util.stream.Stream;
8   
9   import dev.metaschema.core.metapath.StaticContext;
10  import dev.metaschema.core.metapath.format.IPathFormatter;
11  import dev.metaschema.core.metapath.format.IPathSegment;
12  import dev.metaschema.core.metapath.item.IItem;
13  import dev.metaschema.core.metapath.item.IItemVisitor;
14  import dev.metaschema.core.metapath.type.IItemType;
15  import dev.metaschema.core.model.IResourceLocation;
16  import dev.metaschema.core.qname.IEnhancedQName;
17  import dev.metaschema.core.util.ObjectUtils;
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  /**
22   * Represents a Metapath model node.
23   */
24  public interface INodeItem extends IItem, IPathSegment, INodeItemVisitable {
25    /**
26     * The type of node.
27     */
28    enum NodeType {
29      /** A Metaschema module node. */
30      MODULE,
31      /** A document node representing the root of a data instance. */
32      DOCUMENT,
33      /** An assembly node containing fields and other assemblies. */
34      ASSEMBLY,
35      /** A field node containing flags and a value. */
36      FIELD,
37      /** A flag node containing a simple value. */
38      FLAG;
39    }
40  
41    /**
42     * Get the node type for the node item.
43     *
44     * @return the node type
45     */
46    @NonNull
47    NodeType getNodeType();
48  
49    /**
50     * Get the static type information of the node item.
51     *
52     * @return the item type
53     */
54    @NonNull
55    static IItemType type() {
56      return IItemType.node();
57    }
58  
59    /**
60     * Retrieve the relative position of this node relative to sibling nodes.
61     * <p>
62     * A singleton item in a sequence will have a position value of {@code 1}.
63     * <p>
64     * The value {@code 1} is used as the starting value to align with the XPath
65     * specification.
66     *
67     * @return a positive integer value designating this instance's position within
68     *         a collection
69     */
70    default int getPosition() {
71      // only model node items have positions other than 1
72      return 1;
73    }
74  
75    /**
76     * Generate a path for this node in the directed node graph, using the provided
77     * path formatter.
78     */
79    @Override
80    String format(IPathFormatter formatter);
81  
82    /**
83     * Gets the value of the provided node item.
84     * <p>
85     * If the provided node item is a document, this method get the first child node
86     * item's value, since a document doesn't have a value.
87     *
88     * @param <CLASS>
89     *          the type of the bound object to return
90     * @param item
91     *          the node item to get the value of
92     * @return a bound object
93     * @throws NullPointerException
94     *           if the node item has no associated value
95     */
96    @SuppressWarnings("unchecked")
97    @NonNull
98    static <CLASS> CLASS toValue(@NonNull INodeItem item) {
99      INodeItem valuedItem;
100     if (item instanceof IDocumentNodeItem) {
101       // get first child item, since the document has no value
102       valuedItem = item.modelItems().findFirst().get();
103     } else {
104       valuedItem = item;
105     }
106     return ObjectUtils.requireNonNull((CLASS) valuedItem.getValue());
107   }
108 
109   /**
110    * Retrieve the parent node item if it exists.
111    *
112    * @return the parent node item, or {@code null} if this node item has no known
113    *         parent
114    */
115   INodeItem getParentNodeItem();
116 
117   /**
118    * Retrieve the parent content node item if it exists. A content node is a
119    * non-document node.
120    *
121    * @return the parent content node item, or {@code null} if this node item has
122    *         no known parent content node item
123    */
124   IModelNodeItem<?, ?> getParentContentNodeItem();
125 
126   /**
127    * Get the kind of node item this is.
128    *
129    * @return the node item's kind
130    */
131   @NonNull
132   NodeItemKind getNodeItemKind();
133 
134   /**
135    * Retrieve the base URI of this node.
136    * <p>
137    * The base URI of a node will be in order of preference:
138    * <ol>
139    * <li>the base URI defined on the node
140    * <li>the base URI defined on the nearest ancestor node
141    * <li>the base URI defined on the document node
142    * <li>{@code null} if the document node is unknown
143    * </ol>
144    *
145    * @return the base URI or {@code null} if it is unknown
146    */
147   URI getBaseUri();
148 
149   /**
150    * Get the path for this node item as a Metapath.
151    *
152    * @return the Metapath
153    */
154   @NonNull
155   default String getMetapath() {
156     return toPath(IPathFormatter.METAPATH_PATH_FORMATER);
157   }
158 
159   @Override
160   default Stream<? extends INodeItem> getPathStream() {
161     INodeItem parent = getParentNodeItem();
162     return ObjectUtils.notNull(
163         parent == null ? Stream.of(this) : Stream.concat(getParentNodeItem().getPathStream(), Stream.of(this)));
164   }
165 
166   /**
167    * Get a stream of all ancestors of this node item. The stream is ordered from
168    * closest to farthest ancestor.
169    *
170    * @return a stream of ancestor node items
171    */
172   @NonNull
173   default Stream<? extends INodeItem> ancestor() {
174     return ancestorsOf(this);
175   }
176 
177   /**
178    * Get a stream of this and all ancestors of this node item. The stream is
179    * ordered from self, then closest to farthest ancestor.
180    *
181    * @return a stream of this node followed by all ancestor node items
182    */
183   @NonNull
184   default Stream<? extends INodeItem> ancestorOrSelf() {
185     return ObjectUtils.notNull(Stream.concat(ancestor(), Stream.of(this)));
186   }
187 
188   /**
189    * Get a stream of the ancestors of the provided {@code item}. The stream is
190    * ordered from the farthest ancestor to the closest.
191    *
192    * @param item
193    *          the target item to get ancestors for
194    *
195    * @return a stream of all ancestor node items
196    */
197   @NonNull
198   static Stream<? extends INodeItem> ancestorsOf(@NonNull INodeItem item) {
199     INodeItem parent = item.getParentNodeItem();
200     return ObjectUtils.notNull(parent == null ? Stream.empty() : Stream.concat(ancestorsOf(parent), Stream.of(parent)));
201   }
202 
203   /**
204    * Get a stream of all descendant model items of this node item. The stream is
205    * ordered from closest to farthest descendants in a depth-first order.
206    *
207    * @return a stream of descendant node items
208    */
209   @NonNull
210   default Stream<? extends IModelNodeItem<?, ?>> descendant() {
211     return decendantsOf(this);
212   }
213 
214   /**
215    * Get a stream of all descendant model items of the provided {@code item}. The
216    * stream is ordered from closest to farthest descendants in a depth-first
217    * order.
218    *
219    * @param item
220    *          the target item to get descendants for
221    *
222    * @return a stream of descendant node items
223    */
224   @NonNull
225   static Stream<? extends IModelNodeItem<?, ?>> decendantsOf(@NonNull INodeItem item) {
226     Stream<? extends IModelNodeItem<?, ?>> children = item.modelItems();
227 
228     return ObjectUtils.notNull(children.flatMap(child -> {
229       assert child != null;
230       return Stream.concat(Stream.of(child), decendantsOf(child));
231     }));
232   }
233 
234   /**
235    * Get a stream of this node, followed by all descendant model items of this
236    * node item. The stream is ordered from closest to farthest descendants in a
237    * depth-first order.
238    *
239    * @return a stream of this node and descendant node items
240    */
241   @NonNull
242   default Stream<? extends INodeItem> descendantOrSelf() {
243     return ObjectUtils.notNull(Stream.concat(Stream.of(this), descendant()));
244   }
245 
246   /**
247    * Get the children of this node's parent that occur after this node in a
248    * depth-first order.
249    *
250    * @return a stream of nodes
251    */
252   @NonNull
253   default Stream<? extends IModelNodeItem<?, ?>> followingSibling() {
254     return ObjectUtils.notNull(Stream.empty());
255   }
256 
257   /**
258    * Get the children of this node's parent, and their descendants, that occur
259    * before this node in a depth-first order.
260    *
261    * @return a stream of nodes
262    */
263   @NonNull
264   default Stream<? extends IModelNodeItem<?, ?>> precedingSibling() {
265     return ObjectUtils.notNull(Stream.empty());
266   }
267 
268   /**
269    * Get the children of this node's parent, and their descendants, that occur
270    * before this node in a depth-first order.
271    *
272    * @return a stream of nodes
273    */
274   default Stream<? extends IModelNodeItem<?, ?>> following() {
275     return ObjectUtils.notNull(Stream.empty());
276   }
277 
278   /**
279    * Get the children of this node's parent, and their descendants, that occur
280    * after this node in a depth-first order.
281    *
282    * @return a stream of nodes
283    */
284   default Stream<? extends IModelNodeItem<?, ?>> preceding() {
285     return ObjectUtils.notNull(Stream.empty());
286   }
287 
288   /**
289    * Get the flags and value data associated this node. The resulting collection
290    * is expected to be ordered, with the results in document order.
291    * <p>
292    * The resulting collection may be modified, but such modification is not thread
293    * safe
294    *
295    * @return a collection of flags
296    */
297   @NonNull
298   Collection<? extends IFlagNodeItem> getFlags();
299 
300   /**
301    * Lookup a flag and value data on this node by it's effective qualified name.
302    *
303    * @param name
304    *          the effective qualified name of the flag
305    * @return the flag with the matching effective name or {@code null} if no match
306    *         was found
307    */
308   @Nullable
309   IFlagNodeItem getFlagByName(@NonNull IEnhancedQName name);
310 
311   /**
312    * Get the flags and value data associated with this node as a stream.
313    *
314    * @return the stream of flags or an empty stream if none exist
315    */
316   @SuppressWarnings("null")
317   @NonNull
318   default Stream<? extends IFlagNodeItem> flags() {
319     return getFlags().stream();
320   }
321 
322   /**
323    * Get the model items (i.e., fields, assemblies) and value data associated this
324    * node. A given model instance can be multi-valued, so the value of each
325    * instance will be a list. The resulting collection is expected to be ordered,
326    * with the results in document order.
327    * <p>
328    * The resulting collection may be modified, but such modification is not thread
329    * safe
330    *
331    * @return a collection of list(s), with each list containing the items for a
332    *         given model instance
333    */
334   @NonNull
335   Collection<? extends List<? extends IModelNodeItem<?, ?>>> getModelItems();
336 
337   /**
338    * Get the collection of model items associated with the instance having the
339    * provided {@code name}.
340    * <p>
341    * The resulting collection may be modified, but such modification is not thread
342    * safe
343    *
344    * @param name
345    *          the instance name to get model items for
346    * @return the sequence of items associated with the named model instance, or an
347    *         empty list if an instance with that name is not present
348    */
349   @NonNull
350   List<? extends IModelNodeItem<?, ?>> getModelItemsByName(@NonNull IEnhancedQName name);
351 
352   /**
353    * Get the model items (i.e., fields, assemblies) and value data associated this
354    * node as a stream.
355    *
356    * @return the stream of model items or an empty stream if none exist
357    */
358   @SuppressWarnings("null")
359   @NonNull
360   default Stream<? extends IModelNodeItem<?, ?>> modelItems() {
361     return getModelItems().stream().flatMap(Collection::stream);
362   }
363 
364   /**
365    * Get the resource location information for the node, if known.
366    *
367    * @return the resource location information, or {@code null} if not known
368    */
369   @Nullable
370   IResourceLocation getLocation();
371 
372   /**
373    * Get the string value of the node.
374    *
375    * @return the string value of the node or an empty string if it has no value.
376    */
377   @NonNull
378   String stringValue();
379 
380   /**
381    * Get the static context to use to query this node item.
382    *
383    * @return the static context
384    */
385   @NonNull
386   StaticContext getStaticContext();
387 
388   @Override
389   default void accept(IItemVisitor visitor) {
390     visitor.visit(this);
391   }
392 }