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.function.Predicate;
9   import java.util.stream.Stream;
10  
11  import dev.metaschema.core.metapath.DynamicContext;
12  import dev.metaschema.core.metapath.cst.AbstractExpression;
13  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
14  import dev.metaschema.core.metapath.item.ISequence;
15  import dev.metaschema.core.metapath.item.ItemUtils;
16  import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
17  import dev.metaschema.core.metapath.item.node.INodeItem;
18  import dev.metaschema.core.util.ObjectUtils;
19  import edu.umd.cs.findbugs.annotations.NonNull;
20  import edu.umd.cs.findbugs.annotations.Nullable;
21  
22  /**
23   * The CST node for a Metapath
24   * <a href="https://www.w3.org/TR/xpath-31/#doc-xpath31-Wildcard">wildcard name
25   * test</a>.
26   */
27  @SuppressWarnings("PMD.TestClassWithoutTestCases")
28  public class WildcardNodeTest
29      extends AbstractExpression
30      implements INodeTestExpression {
31    @Nullable
32    private final Predicate<IDefinitionNodeItem<?, ?>> matcher;
33  
34    /**
35     * Construct a new wildcard name test expression using the provided matcher.
36     *
37     * @param text
38     *          the parsed text of the expression
39     * @param matcher
40     *          the matcher used to determine matching nodes
41     */
42    public WildcardNodeTest(@NonNull String text, @Nullable IWildcardMatcher matcher) {
43      super(text);
44      this.matcher = matcher;
45    }
46  
47    @Override
48    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
49      return visitor.visitWildcardNodeTest(this, context);
50    }
51  
52    @Override
53    protected ISequence<? extends INodeItem> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
54      Stream<INodeItem> stream = focus.stream()
55          .map(item -> ItemUtils.checkItemIsNodeItem(dynamicContext, item));
56  
57      if (matcher != null) {
58        stream = stream.filter(this::match);
59      }
60  
61      return ISequence.of(ObjectUtils.notNull(stream));
62    }
63  
64    /**
65     * {@inheritDoc}
66     * <p>
67     * If no matcher is provided, this method is a no-op.
68     */
69    @Override
70    @NonNull
71    public <T extends INodeItem> Stream<T> filterStream(@NonNull Stream<T> items) {
72      Stream<T> nodes = items;
73      if (matcher != null) {
74        nodes = INodeTestExpression.super.filterStream(nodes);
75      }
76      return nodes;
77    }
78  
79    /**
80     * Check the provided item to determine if it matches the wildcard.
81     *
82     * @param item
83     *          the item to check for a match
84     * @return {@code true} if the item matches or {@code false} otherwise
85     */
86    @Override
87    public boolean match(@NonNull INodeItem item) {
88      assert matcher != null;
89      Predicate<IDefinitionNodeItem<?, ?>> test = matcher;
90      return !(item instanceof IDefinitionNodeItem) ||
91          test.test((IDefinitionNodeItem<?, ?>) item);
92    }
93  
94    @SuppressWarnings("null")
95    @Override
96    public String toCSTString() {
97      return String.format("%s[%s]", getClass().getName(), matcher == null ? "*:*" : matcher.toString());
98    }
99  }