1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst.math;
7   
8   import dev.metaschema.core.metapath.DynamicContext;
9   import dev.metaschema.core.metapath.IExpression;
10  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
11  import dev.metaschema.core.metapath.function.FunctionUtils;
12  import dev.metaschema.core.metapath.function.impl.OperationFunctions;
13  import dev.metaschema.core.metapath.item.ISequence;
14  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
15  import dev.metaschema.core.metapath.item.atomic.INumericItem;
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import edu.umd.cs.findbugs.annotations.Nullable;
18  
19  /**
20   * An XPath 3.1
21   * <a href="https://www.w3.org/TR/xpath-31/#id-arithmetic">arithmetic
22   * expression</a> supporting modulo.
23   * <p>
24   * For example: {@code 5 mod 2} evaluates to {@code 1}
25   */
26  public class Modulo
27      extends AbstractArithmeticExpression<INumericItem> {
28  
29    /**
30     * Create an expression that gets the numeric remainder from dividing the
31     * dividend by the divisor, also called the "modulo operation".
32     *
33     * @param text
34     *          the parsed text of the expression
35     * @param dividend
36     *          the item to be divided
37     * @param divisor
38     *          the item to divide by
39     */
40    public Modulo(
41        @NonNull String text,
42        @NonNull IExpression dividend,
43        @NonNull IExpression divisor) {
44      super(text, dividend, divisor, INumericItem.class);
45    }
46  
47    @Override
48    public Class<INumericItem> getBaseResultType() {
49      return INumericItem.class;
50    }
51  
52    @Override
53    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
54      return visitor.visitModulo(this, context);
55    }
56  
57    @Override
58    protected ISequence<? extends INumericItem> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
59      IAnyAtomicItem leftItem = ISequence.of(getLeft().accept(dynamicContext, focus).atomize()).getFirstItem(true);
60      IAnyAtomicItem rightItem = ISequence.of(getRight().accept(dynamicContext, focus).atomize()).getFirstItem(true);
61      INumericItem dividend = leftItem == null ? null : FunctionUtils.castToNumeric(leftItem);
62      INumericItem divisor = rightItem == null ? null : FunctionUtils.castToNumeric(rightItem);
63      return resultOrEmpty(dividend, divisor);
64    }
65  
66    /**
67     * Get the numeric remainder from dividing the dividend by the divisor.
68     *
69     * @param dividend
70     *          the item to be divided
71     * @param divisor
72     *          the item to divide by
73     * @return the remainder or an empty {@link ISequence} if either item is
74     *         {@code null}
75     */
76    @NonNull
77    protected static ISequence<? extends INumericItem> resultOrEmpty(@Nullable INumericItem dividend,
78        @Nullable INumericItem divisor) {
79      ISequence<? extends INumericItem> retval;
80      if (dividend == null || divisor == null) {
81        retval = ISequence.empty();
82      } else {
83        INumericItem result = OperationFunctions.opNumericMod(dividend, divisor);
84        retval = ISequence.of(result);
85      }
86      return retval;
87    }
88  }