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 private final Chronology chrono; 113 /** 114 * The number of years. 115 */ 116 final int years; 117 /** 118 * The number of months. 119 */ 120 final int months; 121 /** 122 * The number of days. 123 */ 124 final int days; 125 126 /** 127 * Creates an instance. 128 */ 129 ChronoPeriodImpl(Chronology chrono, int years, int months, int days) { 130 Objects.requireNonNull(chrono, "chrono"); 131 this.chrono = chrono; 132 this.years = years; 133 this.months = months; 134 this.days = days; 135 } 136 137 //----------------------------------------------------------------------- 138 @Override 139 public long get(TemporalUnit unit) { 140 if (unit == ChronoUnit.YEARS) { 141 return years; 142 } else if (unit == ChronoUnit.MONTHS) { 143 return months; 144 } else if (unit == ChronoUnit.DAYS) { 145 return days; 146 } else { 147 throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); 148 } 149 } 150 151 @Override 152 public List<TemporalUnit> getUnits() { 153 return ChronoPeriodImpl.SUPPORTED_UNITS; 154 } 155 156 @Override 157 public Chronology getChronology() { 158 return chrono; 159 } 160 161 //----------------------------------------------------------------------- 162 @Override 163 public boolean isZero() { 164 return years == 0 && months == 0 && days == 0; 165 } 166 167 @Override 168 public boolean isNegative() { 169 return years < 0 || months < 0 || days < 0; 170 } 171 172 //----------------------------------------------------------------------- 173 @Override 174 public ChronoPeriod plus(TemporalAmount amountToAdd) { 175 ChronoPeriodImpl amount = validateAmount(amountToAdd); 176 return new ChronoPeriodImpl( 177 chrono, 178 Math.addExact(years, amount.years), 179 Math.addExact(months, amount.months), 180 Math.addExact(days, amount.days)); 181 } 182 183 @Override 184 public ChronoPeriod minus(TemporalAmount amountToSubtract) { 185 ChronoPeriodImpl amount = validateAmount(amountToSubtract); 186 return new ChronoPeriodImpl( 187 chrono, 188 Math.subtractExact(years, amount.years), 189 Math.subtractExact(months, amount.months), 190 Math.subtractExact(days, amount.days)); 191 } 192 193 /** 194 * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount. 195 * 196 * @param amount the temporal amount to convert, not null 197 * @return the period, not null 198 */ 199 private ChronoPeriodImpl validateAmount(TemporalAmount amount) { 200 Objects.requireNonNull(amount, "amount"); 201 if (amount instanceof ChronoPeriodImpl == false) { 202 throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass()); 203 } 204 ChronoPeriodImpl period = (ChronoPeriodImpl) amount; 205 if (chrono.equals(period.getChronology()) == false) { 206 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId()); 207 } 208 return period; 209 } 210 211 //----------------------------------------------------------------------- 212 @Override 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 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 */ 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 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 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 */ 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 318 public boolean equals(Object obj) { 319 if (this == obj) { 320 return true; 321 } 322 if (obj instanceof ChronoPeriodImpl) { 323 ChronoPeriodImpl other = (ChronoPeriodImpl) obj; 324 return years == other.years && months == other.months && 325 days == other.days && chrono.equals(other.chrono); 326 } 327 return false; 328 } 329 330 @Override 331 public int hashCode() { 332 return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode(); 333 } 334 335 //----------------------------------------------------------------------- 336 @Override 337 public String toString() { 338 if (isZero()) { 339 return getChronology().toString() + " P0D"; 340 } else { 341 StringBuilder buf = new StringBuilder(); 342 buf.append(getChronology().toString()).append(' ').append('P'); 343 if (years != 0) { 344 buf.append(years).append('Y'); 345 } 346 if (months != 0) { 347 buf.append(months).append('M'); 348 } 349 if (days != 0) { 350 buf.append(days).append('D'); 351 } 352 return buf.toString(); 353 } 354 } 355 356 //----------------------------------------------------------------------- 357 /** 358 * Writes the Chronology using a 359 * <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. 360 * <pre> 361 * out.writeByte(12); // identifies this as a ChronoPeriodImpl 362 * out.writeUTF(getId()); // the chronology 363 * out.writeInt(years); 364 * out.writeInt(months); 365 * out.writeInt(days); 366 * </pre> 367 * 368 * @return the instance of {@code Ser}, not null 369 */ 370 @java.io.Serial 371 protected Object writeReplace() { 372 return new Ser(Ser.CHRONO_PERIOD_TYPE, this); 373 } 374 375 /** 376 * Defend against malicious streams. 377 * 378 * @param s the stream to read 379 * @throws InvalidObjectException always 380 */ 381 @java.io.Serial 382 private void readObject(ObjectInputStream s) throws ObjectStreamException { 383 throw new InvalidObjectException("Deserialization via serialization delegate"); 384 } 385 386 void writeExternal(DataOutput out) throws IOException { 387 out.writeUTF(chrono.getId()); 388 out.writeInt(years); 389 out.writeInt(months); 390 out.writeInt(days); 391 } 392 393 static ChronoPeriodImpl readExternal(DataInput in) throws IOException { 394 Chronology chrono = Chronology.of(in.readUTF()); 395 int years = in.readInt(); 396 int months = in.readInt(); 397 int days = in.readInt(); 398 return new ChronoPeriodImpl(chrono, years, months, days); 399 } 400 401 }