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.time.Period;
9   
10  import dev.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
11  import dev.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
12  import dev.metaschema.core.metapath.item.atomic.impl.YearMonthDurationItemImpl;
13  import dev.metaschema.core.metapath.type.IAtomicOrUnionType;
14  import dev.metaschema.core.metapath.type.InvalidTypeMetapathException;
15  import dev.metaschema.core.util.ObjectUtils;
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  
18  /**
19   * An atomic Metapath item containing a duration data value in years, months,
20   * and days.
21   */
22  public interface IYearMonthDurationItem extends IDurationItem {
23    /**
24     * Get the type information for this item.
25     *
26     * @return the type information
27     */
28    @NonNull
29    static IAtomicOrUnionType<IYearMonthDurationItem> type() {
30      return MetaschemaDataTypeProvider.YEAR_MONTH_DURATION.getItemType();
31    }
32  
33    @Override
34    default IAtomicOrUnionType<IYearMonthDurationItem> getType() {
35      return type();
36    }
37  
38    /**
39     * Construct a new year month day duration item using the provided string
40     * {@code value}.
41     *
42     * @param value
43     *          a string representing a year month day duration
44     * @return the new item
45     * @throws InvalidTypeMetapathException
46     *           if the provided string value is not a day/time duration value
47     *           according to ISO 8601
48     */
49    @NonNull
50    static IYearMonthDurationItem valueOf(@NonNull String value) {
51      try {
52        Period period = ObjectUtils.notNull(MetaschemaDataTypeProvider.YEAR_MONTH_DURATION.parse(value).withDays(0));
53        return valueOf(period);
54      } catch (IllegalArgumentException ex) {
55        throw new InvalidTypeMetapathException(
56            null,
57            String.format("Invalid year/month duration value '%s'.",
58                value),
59            ex);
60      }
61    }
62  
63    /**
64     * Construct a new year month day duration item using the provided
65     * {@code value}.
66     *
67     * @param value
68     *          a duration
69     * @return the new item
70     */
71    @NonNull
72    static IYearMonthDurationItem valueOf(@NonNull Period value) {
73      return new YearMonthDurationItemImpl(ObjectUtils.notNull(value.withDays(0)));
74    }
75  
76    /**
77     * Construct a new year month day duration item using the provided values.
78     *
79     * @param years
80     *          the number of years in the period
81     * @param months
82     *          the number of months in the period
83     * @return the new item
84     */
85    @NonNull
86    static IYearMonthDurationItem valueOf(int years, int months) {
87      return valueOf(ObjectUtils.notNull(Period.of(years, months, 0)));
88    }
89  
90    /**
91     * Get the "wrapped" duration value.
92     *
93     * @return the underlying duration value
94     */
95    @NonNull
96    Period asPeriod();
97  
98    /**
99     * Get the total quantity of months this duration represents.
100    *
101    * @return the total months
102    */
103   default long asTotalMonths() {
104     return asPeriod().toTotalMonths();
105   }
106 
107   /**
108    * Returns a new instance with each amount in this period negated.
109    *
110    * @return the new negated instance
111    */
112   @NonNull
113   default IYearMonthDurationItem negate() {
114     return valueOf(ObjectUtils.notNull(asPeriod().negated()));
115   }
116 
117   /**
118    * Cast the provided type to this item type.
119    *
120    * @param item
121    *          the item to cast
122    * @return the original item if it is already this type, otherwise a new item
123    *         cast to this type
124    * @throws InvalidValueForCastFunctionException
125    *           if the provided {@code item} cannot be cast to this type
126    */
127   @NonNull
128   static IYearMonthDurationItem cast(@NonNull IAnyAtomicItem item) {
129     try {
130       return item instanceof IYearMonthDurationItem
131           ? (IYearMonthDurationItem) item
132           : valueOf(item.asString());
133     } catch (IllegalStateException | InvalidTypeMetapathException ex) {
134       // asString can throw IllegalStateException exception
135       throw new InvalidValueForCastFunctionException(ex);
136     }
137   }
138 
139   @Override
140   default IYearMonthDurationItem castAsType(IAnyAtomicItem item) {
141     return cast(item);
142   }
143 
144   /**
145    * Compares this value with the argument.
146    *
147    * @param item
148    *          the item to compare with this value
149    * @return a negative integer, zero, or a positive integer if this value is less
150    *         than, equal to, or greater than the {@code item}.
151    */
152   default int compareTo(IYearMonthDurationItem item) {
153     Period thisPeriod = asPeriod().normalized();
154     Period thatPeriod = item.asPeriod().normalized();
155 
156     int result = Integer.compare(thisPeriod.getYears(), thatPeriod.getYears());
157     return result == 0 ? Integer.compare(thisPeriod.getMonths(), thatPeriod.getMonths()) : result;
158   }
159 }