1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.item.atomic;
7   
8   import java.math.BigInteger;
9   
10  import dev.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
11  import dev.metaschema.core.metapath.function.ArithmeticFunctionException;
12  import dev.metaschema.core.metapath.function.CastFunctionException;
13  import dev.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
14  import dev.metaschema.core.metapath.item.atomic.impl.IntegerItemImpl;
15  import dev.metaschema.core.metapath.type.IAtomicOrUnionType;
16  import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
17  import dev.metaschema.core.util.ObjectUtils;
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  
20  /**
21   * An atomic Metapath item containing an integer data value.
22   */
23  public interface IIntegerItem extends IDecimalItem {
24    /**
25     * The integer value "1".
26     */
27    @NonNull
28    IIntegerItem ONE = valueOf(ObjectUtils.notNull(BigInteger.ONE));
29    /**
30     * The integer value "0".
31     */
32    @NonNull
33    IIntegerItem ZERO = valueOf(ObjectUtils.notNull(BigInteger.ZERO));
34    /**
35     * The integer value "-1".
36     */
37    @NonNull
38    IIntegerItem NEGATIVE_ONE = valueOf(ObjectUtils.notNull(BigInteger.ONE.negate()));
39  
40    /**
41     * Get the type information for this item.
42     *
43     * @return the type information
44     */
45    @NonNull
46    static IAtomicOrUnionType<IIntegerItem> type() {
47      return MetaschemaDataTypeProvider.INTEGER.getItemType();
48    }
49  
50    @Override
51    default IAtomicOrUnionType<? extends IIntegerItem> getType() {
52      return type();
53    }
54  
55    /**
56     * Create an item from an existing integer value.
57     *
58     * @param value
59     *          a string representing an integer value
60     * @return the item
61     * @throws InvalidTypeMetapathException
62     *           if the provided value is not an integer
63     */
64    @NonNull
65    static IIntegerItem valueOf(@NonNull String value) {
66      try {
67        return valueOf(MetaschemaDataTypeProvider.INTEGER.parse(value));
68      } catch (IllegalArgumentException ex) {
69        throw new InvalidTypeMetapathException(null, ex.getMessage(), ex);
70      }
71    }
72  
73    /**
74     * Construct a new integer item using the provided {@code value}.
75     *
76     * @param value
77     *          a long value
78     * @return the new item
79     */
80    @NonNull
81    static IIntegerItem valueOf(int value) {
82      @SuppressWarnings("null")
83      @NonNull
84      BigInteger bigInteger = BigInteger.valueOf(value);
85      return valueOf(bigInteger);
86    }
87  
88    /**
89     * Construct a new integer item using the provided {@code value}.
90     *
91     * @param value
92     *          a long value
93     * @return the new item
94     */
95    @NonNull
96    static IIntegerItem valueOf(long value) {
97      @SuppressWarnings("null")
98      @NonNull
99      BigInteger bigInteger = BigInteger.valueOf(value);
100     return valueOf(bigInteger);
101   }
102 
103   /**
104    * Construct a new integer item using the provided {@code value}.
105    *
106    * @param value
107    *          a long value
108    * @return the new item
109    */
110   @NonNull
111   static IIntegerItem valueOf(boolean value) {
112     return valueOf(ObjectUtils.notNull(value ? BigInteger.ONE : BigInteger.ZERO));
113   }
114 
115   /**
116    * Construct a new integer item using the provided {@code value}.
117    *
118    * @param value
119    *          an integer value
120    * @return the new item
121    */
122   @NonNull
123   static IIntegerItem valueOf(@NonNull BigInteger value) {
124     int signum = value.signum();
125 
126     IIntegerItem retval;
127     if (signum == -1) { // negative
128       retval = new IntegerItemImpl(value);
129     } else if (signum == 0) { // zero
130       retval = INonNegativeIntegerItem.valueOf(value);
131     } else { // positive
132       retval = IPositiveIntegerItem.valueOf(value);
133     }
134     return retval;
135   }
136 
137   /**
138    * Cast the provided type to this item type.
139    *
140    * @param item
141    *          the item to cast
142    * @return the original item if it is already this type, otherwise a new item
143    *         cast to this type
144    * @throws InvalidValueForCastFunctionException
145    *           if the provided {@code item} cannot be cast to this type
146    */
147   @NonNull
148   static IIntegerItem cast(@NonNull IAnyAtomicItem item) {
149     IIntegerItem retval;
150     if (item instanceof IIntegerItem) {
151       retval = (IIntegerItem) item;
152     } else if (item instanceof INumericItem) {
153       retval = valueOf(((INumericItem) item).asInteger());
154     } else if (item instanceof IBooleanItem) {
155       retval = valueOf(((IBooleanItem) item).toBoolean());
156     } else {
157       try {
158         retval = valueOf(item.asString());
159       } catch (IllegalStateException | InvalidTypeMetapathException ex) {
160         // asString can throw IllegalStateException exception
161         throw new InvalidValueForCastFunctionException(ex);
162       }
163     }
164     return retval;
165   }
166 
167   @Override
168   default IIntegerItem castAsType(IAnyAtomicItem item) {
169     return cast(item);
170   }
171 
172   @Override
173   default IIntegerItem abs() {
174     BigInteger value = asInteger();
175     return value.signum() > -1 ? this : valueOf(ObjectUtils.notNull(value.abs()));
176   }
177 
178   @Override
179   default IIntegerItem ceiling() {
180     return this;
181   }
182 
183   @Override
184   default IIntegerItem floor() {
185     return this;
186   }
187 
188   /**
189    * Convert this integer item to a Java int, precisely.
190    *
191    * @return the int value
192    * @throws CastFunctionException
193    *           if the value does not fit in an int
194    */
195   @Override
196   default int toIntValueExact() {
197     // asInteger() is well-defined for integer items, so this should be safe
198     try {
199       return asInteger().intValueExact();
200     } catch (ArithmeticException ex) {
201       throw new CastFunctionException(
202           CastFunctionException.INPUT_VALUE_TOO_LARGE,
203           this,
204           String.format("Integer value '%s' is out of range for a Java int.", asString()),
205           ex);
206     }
207   }
208 
209   /**
210    * Create a new sum by adding this value to the provided addend value.
211    *
212    * @param addend
213    *          the second value to sum
214    * @return a new value resulting from adding this value to the provided addend
215    *         value
216    */
217   @NonNull
218   default IIntegerItem add(@NonNull IIntegerItem addend) {
219     BigInteger addendLeft = asInteger();
220     BigInteger addendRight = addend.asInteger();
221     return valueOf(ObjectUtils.notNull(addendLeft.add(addendRight)));
222   }
223 
224   /**
225    * Determine the difference by subtracting the provided subtrahend value from
226    * this minuend value.
227    *
228    * @param subtrahend
229    *          the value to subtract
230    * @return a new value resulting from subtracting the subtrahend from the
231    *         minuend
232    */
233   @NonNull
234   default IIntegerItem subtract(@NonNull IIntegerItem subtrahend) {
235     BigInteger minuendInteger = asInteger();
236     BigInteger subtrahendInteger = subtrahend.asInteger();
237     return valueOf(ObjectUtils.notNull(minuendInteger.subtract(subtrahendInteger)));
238   }
239 
240   /**
241    * Multiply this multiplicand value by the provided multiplier value.
242    *
243    * @param multiplier
244    *          the value to multiply by
245    * @return a new value resulting from multiplying the multiplicand by the
246    *         multiplier
247    */
248   @NonNull
249   default IIntegerItem multiply(@NonNull IIntegerItem multiplier) {
250     return valueOf(ObjectUtils.notNull(asInteger().multiply(multiplier.asInteger())));
251   }
252 
253   /**
254    * Divide this dividend value by the provided divisor value using integer
255    * division.
256    *
257    * @param divisor
258    *          the value to divide by
259    * @return a new value resulting from dividing the dividend by the divisor
260    */
261   @NonNull
262   default IIntegerItem integerDivide(@NonNull IIntegerItem divisor) {
263     BigInteger divisorInteger = divisor.asInteger();
264 
265     if (BigInteger.ZERO.equals(divisorInteger)) {
266       throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
267           ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
268     }
269     return valueOf(ObjectUtils.notNull(asInteger().divide(divisorInteger)));
270   }
271 
272   /**
273    * Compute the remainder when dividing this dividend value by the provided
274    * divisor value.
275    *
276    * @param divisor
277    *          the value to divide by
278    * @return a new value containing the remainder resulting from dividing the
279    *         dividend by the divisor
280    */
281   @NonNull
282   default IIntegerItem mod(@NonNull IIntegerItem divisor) {
283     BigInteger divisorInteger = divisor.asInteger();
284 
285     if (BigInteger.ZERO.equals(divisorInteger)) {
286       throw new ArithmeticFunctionException(ArithmeticFunctionException.DIVISION_BY_ZERO,
287           ArithmeticFunctionException.DIVISION_BY_ZERO_MESSAGE);
288     }
289 
290     return valueOf(ObjectUtils.notNull(asInteger().remainder(divisorInteger)));
291   }
292 
293   @Override
294   default IIntegerItem negate() {
295     return valueOf(ObjectUtils.notNull(asInteger().negate()));
296   }
297 
298   /**
299    * Compares this value with the argument.
300    *
301    * @param item
302    *          the item to compare with this value
303    * @return a negative integer, zero, or a positive integer if this value is less
304    *         than, equal to, or greater than the {@code item}.
305    */
306   default int compareTo(IIntegerItem item) {
307     return asInteger().compareTo(item.asInteger());
308   }
309 }