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 java.util.HashMap;
9   import java.util.Map;
10  
11  import dev.metaschema.core.metapath.IExpression;
12  import dev.metaschema.core.metapath.cst.IExpressionVisitor;
13  import dev.metaschema.core.metapath.function.impl.OperationFunctions;
14  import dev.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
15  import dev.metaschema.core.metapath.item.atomic.IDateItem;
16  import dev.metaschema.core.metapath.item.atomic.IDateTimeItem;
17  import dev.metaschema.core.metapath.item.atomic.IDayTimeDurationItem;
18  import dev.metaschema.core.metapath.item.atomic.INumericItem;
19  import dev.metaschema.core.metapath.item.atomic.ITimeItem;
20  import dev.metaschema.core.metapath.item.atomic.IYearMonthDurationItem;
21  import dev.metaschema.core.util.CollectionUtil;
22  import dev.metaschema.core.util.ObjectUtils;
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  
25  /**
26   * Implements the '-' operator for Metapath subtraction operations.
27   * <p>
28   * An XPath 3.1
29   * <a href="https://www.w3.org/TR/xpath-31/#id-arithmetic">arithmetic
30   * expression</a> supporting subtraction.
31   * <p>
32   * Supports subtraction operations between:
33   * <ul>
34   * <li>Numeric values
35   * <li>Dates (returning {@link IDayTimeDurationItem})
36   * <li>DateTimes (returning {@link IDayTimeDurationItem})
37   * <li>Times (returning {@link IDayTimeDurationItem})
38   * <li>Date/DateTime - {@link IYearMonthDurationItem}
39   * <li>Date/DateTime/Time - {@link IDayTimeDurationItem}
40   * <li>{@link IYearMonthDurationItem} - {@link IYearMonthDurationItem}
41   * <li>{@link IDayTimeDurationItem} - {@link IDayTimeDurationItem}
42   * </ul>
43   * <p>
44   * Example Metapath usage:
45   *
46   * <pre>
47   * // Numeric subtraction
48   * 5 - 3 → 2
49   * // Date subtraction
50   * date('2024-01-01') - date('2023-01-01') → duration('P1Y')
51   * // DateTime - Duration
52   * date-time('2024-01-01T00:00:00') - duration('P1D') → date-time('2023-12-31T00:00:00')
53   * </pre>
54   */
55  public class Subtraction
56      extends AbstractBasicArithmeticExpression {
57    @NonNull
58    private static final Map<
59        Class<? extends IAnyAtomicItem>,
60        Map<Class<? extends IAnyAtomicItem>, OperationStrategy>> SUBTRACTION_STRATEGIES = generateStrategies();
61  
62    /**
63     * An expression that gets the difference of two atomic data items.
64     *
65     * @param text
66     *          the parsed text of the expression
67     * @param minuend
68     *          an expression whose result is the value being subtracted from
69     * @param subtrahend
70     *          an expression whose result is the value being subtracted
71     */
72    public Subtraction(
73        @NonNull String text,
74        @NonNull IExpression minuend,
75        @NonNull IExpression subtrahend) {
76      super(text, minuend, subtrahend);
77    }
78  
79    @Override
80    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
81      return visitor.visitSubtraction(this, context);
82    }
83  
84    @Override
85    protected Map<Class<? extends IAnyAtomicItem>, Map<Class<? extends IAnyAtomicItem>, OperationStrategy>>
86        getStrategies() {
87      return SUBTRACTION_STRATEGIES;
88    }
89  
90    @Override
91    protected INumericItem operationAsNumeric(INumericItem left, INumericItem right) {
92      // Default to numeric subtraction
93      return OperationFunctions.opNumericSubtract(left, right);
94    }
95  
96    @Override
97    protected String unsupportedMessage(String left, String right) {
98      return ObjectUtils.notNull(String.format("Subtraction of '%s' by '%s' is not supported.", left, right));
99    }
100 
101   @SuppressWarnings("PMD.UseConcurrentHashMap")
102   @NonNull
103   private static Map<
104       Class<? extends IAnyAtomicItem>,
105       Map<Class<? extends IAnyAtomicItem>, OperationStrategy>> generateStrategies() {
106     // Date strategies
107     Map<Class<? extends IAnyAtomicItem>, OperationStrategy> typeStrategies = new HashMap<>();
108     typeStrategies.put(IDateItem.class,
109         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDates(
110             (IDateItem) minuend,
111             (IDateItem) subtrahend,
112             dynamicContext));
113     typeStrategies.put(IYearMonthDurationItem.class,
114         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractYearMonthDurationFromDate(
115             (IDateItem) minuend,
116             (IYearMonthDurationItem) subtrahend));
117     typeStrategies.put(IDayTimeDurationItem.class,
118         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDayTimeDurationFromDate(
119             (IDateItem) minuend,
120             (IDayTimeDurationItem) subtrahend));
121     Map<
122         Class<? extends IAnyAtomicItem>,
123         Map<Class<? extends IAnyAtomicItem>, OperationStrategy>> strategies = new HashMap<>();
124     strategies.put(IDateItem.class, CollectionUtil.unmodifiableMap(typeStrategies));
125 
126     // DateTime strategies
127     typeStrategies = new HashMap<>();
128     typeStrategies.put(IDateTimeItem.class,
129         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDateTimes(
130             (IDateTimeItem) minuend,
131             (IDateTimeItem) subtrahend,
132             dynamicContext));
133     typeStrategies.put(IYearMonthDurationItem.class,
134         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractYearMonthDurationFromDateTime(
135             (IDateTimeItem) minuend,
136             (IYearMonthDurationItem) subtrahend));
137     typeStrategies.put(IDayTimeDurationItem.class,
138         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDayTimeDurationFromDateTime(
139             (IDateTimeItem) minuend,
140             (IDayTimeDurationItem) subtrahend));
141     strategies.put(IDateTimeItem.class, CollectionUtil.unmodifiableMap(typeStrategies));
142 
143     // Time strategies
144     typeStrategies = new HashMap<>();
145     typeStrategies.put(ITimeItem.class,
146         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractTimes(
147             (ITimeItem) minuend,
148             (ITimeItem) subtrahend,
149             dynamicContext));
150     typeStrategies.put(IDayTimeDurationItem.class,
151         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDayTimeDurationFromTime(
152             (ITimeItem) minuend,
153             (IDayTimeDurationItem) subtrahend));
154     strategies.put(ITimeItem.class, CollectionUtil.unmodifiableMap(typeStrategies));
155 
156     // YearMonthDuration strategies
157     typeStrategies = new HashMap<>();
158     typeStrategies.put(IYearMonthDurationItem.class,
159         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractYearMonthDurations(
160             (IYearMonthDurationItem) minuend,
161             (IYearMonthDurationItem) subtrahend));
162     strategies.put(IYearMonthDurationItem.class, CollectionUtil.unmodifiableMap(typeStrategies));
163 
164     // DayTimeDuration strategies
165     typeStrategies = new HashMap<>();
166     typeStrategies.put(IDayTimeDurationItem.class,
167         (minuend, subtrahend, dynamicContext) -> OperationFunctions.opSubtractDayTimeDurations(
168             (IDayTimeDurationItem) minuend,
169             (IDayTimeDurationItem) subtrahend));
170     strategies.put(IDayTimeDurationItem.class, CollectionUtil.unmodifiableMap(typeStrategies));
171 
172     return CollectionUtil.unmodifiableMap(strategies);
173   }
174 }