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.List;
9   
10  import dev.metaschema.core.metapath.DynamicContext;
11  import dev.metaschema.core.metapath.IExpression;
12  import dev.metaschema.core.metapath.cst.AbstractExpression;
13  import dev.metaschema.core.metapath.cst.ExpressionUtils;
14  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
15  import dev.metaschema.core.metapath.item.IItem;
16  import dev.metaschema.core.metapath.item.ISequence;
17  import dev.metaschema.core.metapath.item.ItemUtils;
18  import dev.metaschema.core.metapath.item.node.INodeItem;
19  import dev.metaschema.core.util.CollectionUtil;
20  import dev.metaschema.core.util.ObjectUtils;
21  import edu.umd.cs.findbugs.annotations.NonNull;
22  import edu.umd.cs.findbugs.annotations.Nullable;
23  
24  /**
25   * An immutable expression that combines the evaluation of a sub-expression,
26   * with the evaluation of a series of predicate expressions that filter the
27   * result of the evaluation.
28   */
29  public class Step
30      extends AbstractExpression {
31  
32    @NonNull
33    private final Axis axisExpression;
34    @Nullable
35    private final INodeTestExpression stepExpression;
36    @NonNull
37    private final Class<? extends IItem> staticResultType;
38  
39    /**
40     * Construct a new step expression expression.
41     *
42     * @param text
43     *          the parsed text of the expression
44     * @param axis
45     *          the axis to evaluate against
46     */
47    public Step(@NonNull String text, @NonNull Axis axis) {
48      this(text, axis, null);
49    }
50  
51    /**
52     * Construct a new step expression expression.
53     *
54     * @param text
55     *          the parsed text of the expression
56     * @param axis
57     *          the axis to evaluate against
58     * @param step
59     *          the optional sub-expression to evaluate before filtering with the
60     *          predicates
61     */
62    public Step(@NonNull String text, @NonNull Axis axis, @Nullable INodeTestExpression step) {
63      super(text);
64      this.axisExpression = axis;
65      this.stepExpression = step;
66      this.staticResultType = ExpressionUtils.analyzeStaticResultType(IItem.class, step == null
67          ? CollectionUtil.emptyList()
68          : CollectionUtil.singletonList(step));
69    }
70  
71    /**
72     * Get the axis to use for the step.
73     *
74     * @return the step axis to use
75     */
76    @NonNull
77    public Axis getAxis() {
78      return axisExpression;
79    }
80  
81    /**
82     * Get the step expression's sub-expression.
83     *
84     * @return the sub-expression or {@code null} if there is no sub-expression
85     */
86    @Nullable
87    public INodeTestExpression getStep() {
88      return stepExpression;
89    }
90  
91    @Override
92    public Class<? extends IItem> getStaticResultType() {
93      return staticResultType;
94    }
95  
96    @Override
97    public List<? extends IExpression> getChildren() {
98      IExpression step = getStep();
99      return step == null ? CollectionUtil.emptyList() : CollectionUtil.singletonList(step);
100   }
101 
102   @Override
103   public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
104     return visitor.visitStep(this, context);
105   }
106 
107   @Override
108   protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
109     Axis axis = getAxis();
110 
111     ISequence<? extends INodeItem> axisResult;
112     if (focus.isEmpty()) {
113       axisResult = ISequence.empty();
114     } else {
115       axisResult = ISequence.of(ObjectUtils.notNull(focus.stream()
116           .map(item -> ItemUtils.checkItemIsNodeItem(dynamicContext, item))
117           .flatMap(item -> {
118             assert item != null;
119             return axis.execute(item);
120           }).distinct()));
121     }
122 
123     IExpression step = getStep();
124     return step == null ? axisResult : step.accept(dynamicContext, axisResult);
125   }
126 
127   @SuppressWarnings("null")
128   @Override
129   public String toCSTString() {
130     return String.format("%s[axis=%s]",
131         getClass().getName(),
132         getAxis().name());
133   }
134 }