1 /*
2  * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * Copyright (c) 2013, Stephen Colebourne & Michael Nascimento Santos
28  *
29  * All rights reserved.
30  *
31  * Redistribution and use in source and binary forms, with or without
32  * modification, are permitted provided that the following conditions are met:
33  *
34  *  * Redistributions of source code must retain the above copyright notice,
35  *    this list of conditions and the following disclaimer.
36  *
37  *  * Redistributions in binary form must reproduce the above copyright notice,
38  *    this list of conditions and the following disclaimer in the documentation
39  *    and/or other materials provided with the distribution.
40  *
41  *  * Neither the name of JSR-310 nor the names of its contributors
42  *    may be used to endorse or promote products derived from this software
43  *    without specific prior written permission.
44  *
45  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
49  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
50  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
51  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
52  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
53  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
54  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
55  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56  */
57 package java.time.chrono;
58 
59 import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
60 import static java.time.temporal.ChronoUnit.DAYS;
61 import static java.time.temporal.ChronoUnit.MONTHS;
62 import static java.time.temporal.ChronoUnit.YEARS;
63 
64 import java.io.DataInput;
65 import java.io.DataOutput;
66 import java.io.IOException;
67 import java.io.InvalidObjectException;
68 import java.io.ObjectInputStream;
69 import java.io.ObjectStreamException;
70 import java.io.Serializable;
71 import java.time.DateTimeException;
72 import java.time.temporal.ChronoUnit;
73 import java.time.temporal.Temporal;
74 import java.time.temporal.TemporalAccessor;
75 import java.time.temporal.TemporalAmount;
76 import java.time.temporal.TemporalQueries;
77 import java.time.temporal.TemporalUnit;
78 import java.time.temporal.UnsupportedTemporalTypeException;
79 import java.time.temporal.ValueRange;
80 import java.util.List;
81 import java.util.Objects;
82 
83 /**
84  * A period expressed in terms of a standard year-month-day calendar system.
85  * <p>
86  * This class is used by applications seeking to handle dates in non-ISO calendar systems.
87  * For example, the Japanese, Minguo, Thai Buddhist and others.
88  *
89  * @implSpec
90  * This class is immutable nad thread-safe.
91  *
92  * @since 1.8
93  */
94 final class ChronoPeriodImpl
95         implements ChronoPeriod, Serializable {
96     // this class is only used by JDK chronology implementations and makes assumptions based on that fact
97 
98     /**
99      * Serialization version.
100      */
101     @java.io.Serial
102     private static final long serialVersionUID = 57387258289L;
103 
104     /**
105      * The set of supported units.
106      */
107     private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS);
108 
109     /**
110      * The chronology.
111      */
112     @SuppressWarnings("serial") // Not statically typed as Serializable
113     private final Chronology chrono;
114     /**
115      * The number of years.
116      */
117     final int years;
118     /**
119      * The number of months.
120      */
121     final int months;
122     /**
123      * The number of days.
124      */
125     final int days;
126 
127     /**
128      * Creates an instance.
129      */
ChronoPeriodImpl(Chronology chrono, int years, int months, int days)130     ChronoPeriodImpl(Chronology chrono, int years, int months, int days) {
131         Objects.requireNonNull(chrono, "chrono");
132         this.chrono = chrono;
133         this.years = years;
134         this.months = months;
135         this.days = days;
136     }
137 
138     //-----------------------------------------------------------------------
139     @Override
get(TemporalUnit unit)140     public long get(TemporalUnit unit) {
141         if (unit == ChronoUnit.YEARS) {
142             return years;
143         } else if (unit == ChronoUnit.MONTHS) {
144             return months;
145         } else if (unit == ChronoUnit.DAYS) {
146             return days;
147         } else {
148             throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
149         }
150     }
151 
152     @Override
getUnits()153     public List<TemporalUnit> getUnits() {
154         return ChronoPeriodImpl.SUPPORTED_UNITS;
155     }
156 
157     @Override
getChronology()158     public Chronology getChronology() {
159         return chrono;
160     }
161 
162     //-----------------------------------------------------------------------
163     @Override
isZero()164     public boolean isZero() {
165         return years == 0 && months == 0 && days == 0;
166     }
167 
168     @Override
isNegative()169     public boolean isNegative() {
170         return years < 0 || months < 0 || days < 0;
171     }
172 
173     //-----------------------------------------------------------------------
174     @Override
plus(TemporalAmount amountToAdd)175     public ChronoPeriod plus(TemporalAmount amountToAdd) {
176         ChronoPeriodImpl amount = validateAmount(amountToAdd);
177         return new ChronoPeriodImpl(
178                 chrono,
179                 Math.addExact(years, amount.years),
180                 Math.addExact(months, amount.months),
181                 Math.addExact(days, amount.days));
182     }
183 
184     @Override
minus(TemporalAmount amountToSubtract)185     public ChronoPeriod minus(TemporalAmount amountToSubtract) {
186         ChronoPeriodImpl amount = validateAmount(amountToSubtract);
187         return new ChronoPeriodImpl(
188                 chrono,
189                 Math.subtractExact(years, amount.years),
190                 Math.subtractExact(months, amount.months),
191                 Math.subtractExact(days, amount.days));
192     }
193 
194     /**
195      * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount.
196      *
197      * @param amount  the temporal amount to convert, not null
198      * @return the period, not null
199      */
validateAmount(TemporalAmount amount)200     private ChronoPeriodImpl validateAmount(TemporalAmount amount) {
201         Objects.requireNonNull(amount, "amount");
202         if (!(amount instanceof ChronoPeriodImpl period)) {
203             throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass());
204         }
205         if (!(chrono.equals(period.getChronology()))) {
206             throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId());
207         }
208         return period;
209     }
210 
211     //-----------------------------------------------------------------------
212     @Override
multipliedBy(int scalar)213     public ChronoPeriod multipliedBy(int scalar) {
214         if (this.isZero() || scalar == 1) {
215             return this;
216         }
217         return new ChronoPeriodImpl(
218                 chrono,
219                 Math.multiplyExact(years, scalar),
220                 Math.multiplyExact(months, scalar),
221                 Math.multiplyExact(days, scalar));
222     }
223 
224     //-----------------------------------------------------------------------
225     @Override
normalized()226     public ChronoPeriod normalized() {
227         long monthRange = monthRange();
228         if (monthRange > 0) {
229             long totalMonths = years * monthRange + months;
230             long splitYears = totalMonths / monthRange;
231             int splitMonths = (int) (totalMonths % monthRange);  // no overflow
232             if (splitYears == years && splitMonths == months) {
233                 return this;
234             }
235             return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days);
236 
237         }
238         return this;
239     }
240 
241     /**
242      * Calculates the range of months.
243      *
244      * @return the month range, -1 if not fixed range
245      */
monthRange()246     private long monthRange() {
247         ValueRange startRange = chrono.range(MONTH_OF_YEAR);
248         if (startRange.isFixed() && startRange.isIntValue()) {
249             return startRange.getMaximum() - startRange.getMinimum() + 1;
250         }
251         return -1;
252     }
253 
254     //-------------------------------------------------------------------------
255     @Override
addTo(Temporal temporal)256     public Temporal addTo(Temporal temporal) {
257         validateChrono(temporal);
258         if (months == 0) {
259             if (years != 0) {
260                 temporal = temporal.plus(years, YEARS);
261             }
262         } else {
263             long monthRange = monthRange();
264             if (monthRange > 0) {
265                 temporal = temporal.plus(years * monthRange + months, MONTHS);
266             } else {
267                 if (years != 0) {
268                     temporal = temporal.plus(years, YEARS);
269                 }
270                 temporal = temporal.plus(months, MONTHS);
271             }
272         }
273         if (days != 0) {
274             temporal = temporal.plus(days, DAYS);
275         }
276         return temporal;
277     }
278 
279 
280 
281     @Override
subtractFrom(Temporal temporal)282     public Temporal subtractFrom(Temporal temporal) {
283         validateChrono(temporal);
284         if (months == 0) {
285             if (years != 0) {
286                 temporal = temporal.minus(years, YEARS);
287             }
288         } else {
289             long monthRange = monthRange();
290             if (monthRange > 0) {
291                 temporal = temporal.minus(years * monthRange + months, MONTHS);
292             } else {
293                 if (years != 0) {
294                     temporal = temporal.minus(years, YEARS);
295                 }
296                 temporal = temporal.minus(months, MONTHS);
297             }
298         }
299         if (days != 0) {
300             temporal = temporal.minus(days, DAYS);
301         }
302         return temporal;
303     }
304 
305     /**
306      * Validates that the temporal has the correct chronology.
307      */
validateChrono(TemporalAccessor temporal)308     private void validateChrono(TemporalAccessor temporal) {
309         Objects.requireNonNull(temporal, "temporal");
310         Chronology temporalChrono = temporal.query(TemporalQueries.chronology());
311         if (temporalChrono != null && chrono.equals(temporalChrono) == false) {
312             throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId());
313         }
314     }
315 
316     //-----------------------------------------------------------------------
317     @Override
equals(Object obj)318     public boolean equals(Object obj) {
319         if (this == obj) {
320             return true;
321         }
322         return (obj instanceof ChronoPeriodImpl other)
323                 && years == other.years && months == other.months
324                 && days == other.days && chrono.equals(other.chrono);
325     }
326 
327     @Override
hashCode()328     public int hashCode() {
329         return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode();
330     }
331 
332     //-----------------------------------------------------------------------
333     @Override
toString()334     public String toString() {
335         if (isZero()) {
336             return getChronology().toString() + " P0D";
337         } else {
338             StringBuilder buf = new StringBuilder();
339             buf.append(getChronology().toString()).append(' ').append('P');
340             if (years != 0) {
341                 buf.append(years).append('Y');
342             }
343             if (months != 0) {
344                 buf.append(months).append('M');
345             }
346             if (days != 0) {
347                 buf.append(days).append('D');
348             }
349             return buf.toString();
350         }
351     }
352 
353     //-----------------------------------------------------------------------
354     /**
355      * Writes the Chronology using a
356      * <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>.
357      * <pre>
358      *  out.writeByte(12);  // identifies this as a ChronoPeriodImpl
359      *  out.writeUTF(getId());  // the chronology
360      *  out.writeInt(years);
361      *  out.writeInt(months);
362      *  out.writeInt(days);
363      * </pre>
364      *
365      * @return the instance of {@code Ser}, not null
366      */
367     @java.io.Serial
writeReplace()368     protected Object writeReplace() {
369         return new Ser(Ser.CHRONO_PERIOD_TYPE, this);
370     }
371 
372     /**
373      * Defend against malicious streams.
374      *
375      * @param s the stream to read
376      * @throws InvalidObjectException always
377      */
378     @java.io.Serial
readObject(ObjectInputStream s)379     private void readObject(ObjectInputStream s) throws ObjectStreamException {
380         throw new InvalidObjectException("Deserialization via serialization delegate");
381     }
382 
writeExternal(DataOutput out)383     void writeExternal(DataOutput out) throws IOException {
384         out.writeUTF(chrono.getId());
385         out.writeInt(years);
386         out.writeInt(months);
387         out.writeInt(days);
388     }
389 
readExternal(DataInput in)390     static ChronoPeriodImpl readExternal(DataInput in) throws IOException {
391         Chronology chrono = Chronology.of(in.readUTF());
392         int years = in.readInt();
393         int months = in.readInt();
394         int days = in.readInt();
395         return new ChronoPeriodImpl(chrono, years, months, days);
396     }
397 
398 }
399