1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst.path;
7   
8   import java.util.stream.Stream;
9   
10  import dev.metaschema.core.metapath.DynamicContext;
11  import dev.metaschema.core.metapath.IExpression;
12  import dev.metaschema.core.metapath.item.ISequence;
13  import dev.metaschema.core.metapath.item.ItemUtils;
14  import dev.metaschema.core.metapath.item.node.ICycledAssemblyNodeItem;
15  import dev.metaschema.core.metapath.item.node.INodeItem;
16  import dev.metaschema.core.util.ObjectUtils;
17  import edu.umd.cs.findbugs.annotations.NonNull;
18  
19  /**
20   * A base class for Metapath expressions based on the XPath 3.1 <a href=
21   * "https://www.w3.org/TR/xpath-31/#id-relative-path-expressions">relative path
22   * expressions</a> that entail searching.
23   */
24  public abstract class AbstractSearchPathExpression
25      extends AbstractPathExpression<INodeItem> {
26    @NonNull
27    private final Class<? extends INodeItem> staticResultType;
28  
29    /**
30     * Construct a new relative path expression, used for searching.
31     *
32     * @param text
33     *          the parsed text of the expression
34     * @param staticResultType
35     *          the static result type
36     */
37    public AbstractSearchPathExpression(
38        @NonNull String text,
39        @NonNull Class<? extends INodeItem> staticResultType) {
40      super(text);
41      this.staticResultType = staticResultType;
42    }
43  
44    @Override
45    public final Class<INodeItem> getBaseResultType() {
46      return INodeItem.class;
47    }
48  
49    @Override
50    public Class<? extends INodeItem> getStaticResultType() {
51      return staticResultType;
52    }
53  
54    /**
55     * Evaluate the {@code nodeContext} and its ancestors against the provided
56     * {@code expression}, keeping any matching nodes.
57     *
58     * @param expression
59     *          the expression to evaluate
60     * @param dynamicContext
61     *          the evaluation context
62     * @param outerFocus
63     *          the current context node
64     * @return the matching nodes
65     */
66    @NonNull
67    protected Stream<? extends INodeItem> search(
68        @NonNull IExpression expression,
69        @NonNull DynamicContext dynamicContext,
70        @NonNull ISequence<?> outerFocus) {
71      // ensure the sequence is backed by a list
72      ISequence<?> focus = outerFocus.reusable();
73  
74      // check the current focus
75      @SuppressWarnings("unchecked")
76      Stream<? extends INodeItem> nodeMatches
77          = (Stream<? extends INodeItem>) expression.accept(dynamicContext, focus).stream();
78  
79      Stream<? extends INodeItem> childMatches = focus.stream()
80          .map(item -> ItemUtils.checkItemIsNodeItem(dynamicContext, item))
81          .flatMap(focusedNode -> {
82  
83            Stream<? extends INodeItem> matches;
84            if (focusedNode instanceof ICycledAssemblyNodeItem) {
85              // prevent stack overflow
86              matches = Stream.empty();
87            } else {
88              assert focusedNode != null; // may be null?
89              // create a stream of flags and model elements to check
90              Stream<? extends INodeItem> flags = focusedNode.flags();
91              Stream<? extends INodeItem> modelItems = focusedNode.modelItems();
92  
93              matches = search(
94                  expression,
95                  dynamicContext,
96                  ISequence.of(ObjectUtils.notNull(Stream.concat(flags, modelItems))));
97            }
98            return matches;
99          });
100 
101     return ObjectUtils.notNull(Stream.concat(nodeMatches, childMatches).distinct());
102   }
103 }