1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst.logic;
7   
8   import java.math.BigInteger;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Map.Entry;
12  import java.util.concurrent.atomic.AtomicInteger;
13  import java.util.stream.Collectors;
14  import java.util.stream.Stream;
15  
16  import dev.metaschema.core.metapath.DynamicContext;
17  import dev.metaschema.core.metapath.FocusContext;
18  import dev.metaschema.core.metapath.IExpression;
19  import dev.metaschema.core.metapath.MetapathEvaluationFeature;
20  import dev.metaschema.core.metapath.cst.AbstractExpression;
21  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
22  import dev.metaschema.core.metapath.cst.items.IntegerLiteral;
23  import dev.metaschema.core.metapath.function.library.FnBoolean;
24  import dev.metaschema.core.metapath.item.IItem;
25  import dev.metaschema.core.metapath.item.ISequence;
26  import dev.metaschema.core.util.ObjectUtils;
27  import edu.umd.cs.findbugs.annotations.NonNull;
28  
29  /**
30   * An XPath 3.1
31   * <a href="https://www.w3.org/TR/xpath-31/#id-filter-expression">filter
32   * expression</a> or a
33   * <a href="https://www.w3.org/TR/xpath-31/#id-predicate">predicate
34   * expression</a> in the case where the previous expression is an
35   * <a href="https://www.w3.org/TR/xpath-31/#doc-xpath31-AxisStep">axis step</a>
36   * expression.
37   */
38  public class PredicateExpression
39      extends AbstractExpression {
40    @NonNull
41    private final IExpression base;
42    @NonNull
43    private final List<IExpression> predicates;
44  
45    /**
46     * Construct a new predicate expression.
47     *
48     * @param text
49     *          the parsed text of the expression
50     * @param base
51     *          the base to evaluate against
52     * @param predicates
53     *          the expression(s) to apply as a filter
54     */
55    public PredicateExpression(
56        @NonNull String text,
57        @NonNull IExpression base,
58        @NonNull List<IExpression> predicates) {
59      super(text);
60      this.base = base;
61      this.predicates = predicates;
62    }
63  
64    /**
65     * Get the base sub-expression.
66     *
67     * @return the sub-expression
68     */
69    @NonNull
70    public IExpression getBase() {
71      return base;
72    }
73  
74    /**
75     * Retrieve the list of predicates to filter with.
76     *
77     * @return the list of predicates
78     */
79    @NonNull
80    public List<IExpression> getPredicates() {
81      return predicates;
82    }
83  
84    @Override
85    public List<? extends IExpression> getChildren() {
86      return ObjectUtils.notNull(
87          Stream.concat(Stream.of(getBase()), getPredicates().stream()).collect(Collectors.toList()));
88    }
89  
90    @Override
91    protected ISequence<?> evaluate(@NonNull DynamicContext dynamicContext,
92        @NonNull ISequence<?> focus) {
93  
94      ISequence<?> retval = getBase().accept(dynamicContext, focus);
95  
96      if (dynamicContext.getConfiguration().isFeatureEnabled(MetapathEvaluationFeature.METAPATH_EVALUATE_PREDICATES)) {
97        // evaluate the predicates for this step
98        AtomicInteger index = new AtomicInteger();
99        int size = retval.size();
100 
101       Stream<? extends IItem> stream = ObjectUtils.notNull(
102           retval.stream().map(item -> {
103             assert item != null;
104             int pos = index.incrementAndGet();
105             DynamicContext subContext = dynamicContext.subContext(
106                 FocusContext.of(item, pos, size));
107             return Map.entry(subContext, item);
108           }).filter(entry -> {
109             IItem item = ObjectUtils.notNull(entry.getValue());
110             DynamicContext subContext = ObjectUtils.notNull(entry.getKey());
111 
112             // return false if any predicate evaluates to false
113             return !predicates.stream()
114                 .map(predicateExpr -> {
115                   boolean bool;
116                   if (predicateExpr instanceof IntegerLiteral) {
117                     // reduce the result to the matching item
118                     BigInteger predicateIndex = ((IntegerLiteral) predicateExpr).getValue().asInteger();
119 
120                     // get the position of the item from the context
121                     // focus context is guaranteed non-null since we created it in subContext above
122                     final BigInteger position = BigInteger.valueOf(
123                         ObjectUtils.requireNonNull(subContext.getFocusContext()).getPosition());
124 
125                     // it is a match if the position matches
126                     bool = position.equals(predicateIndex);
127                   } else {
128                     ISequence<?> innerFocus = ISequence.of(item);
129                     ISequence<?> predicateResult = predicateExpr.accept(subContext, innerFocus);
130                     bool = FnBoolean.fnBoolean(predicateResult).toBoolean();
131                   }
132                   return bool;
133                 }).anyMatch(x -> !x);
134           }).map(Entry::getValue));
135 
136       retval = ISequence.of(stream);
137     }
138     return retval;
139   }
140 
141   @Override
142   public <RESULT, CONTEXT> RESULT accept(@NonNull IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
143     return visitor.visitPredicate(this, context);
144   }
145 
146 }