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 */ 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 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 153 public List<TemporalUnit> getUnits() { 154 return ChronoPeriodImpl.SUPPORTED_UNITS; 155 } 156 157 @Override 158 public Chronology getChronology() { 159 return chrono; 160 } 161 162 //----------------------------------------------------------------------- 163 @Override 164 public boolean isZero() { 165 return years == 0 && months == 0 && days == 0; 166 } 167 168 @Override 169 public boolean isNegative() { 170 return years < 0 || months < 0 || days < 0; 171 } 172 173 //----------------------------------------------------------------------- 174 @Override 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 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 */ 200 private ChronoPeriodImpl validateAmount(TemporalAmount amount) { 201 Objects.requireNonNull(amount, "amount"); 202 if (amount instanceof ChronoPeriodImpl == false) { 203 throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass()); 204 } 205 ChronoPeriodImpl period = (ChronoPeriodImpl) amount; 206 if (chrono.equals(period.getChronology()) == false) { 207 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId()); 208 } 209 return period; 210 } 211 212 //----------------------------------------------------------------------- 213 @Override 214 public ChronoPeriod multipliedBy(int scalar) { 215 if (this.isZero() || scalar == 1) { 216 return this; 217 } 218 return new ChronoPeriodImpl( 219 chrono, 220 Math.multiplyExact(years, scalar), 221 Math.multiplyExact(months, scalar), 222 Math.multiplyExact(days, scalar)); 223 } 224 225 //----------------------------------------------------------------------- 226 @Override 227 public ChronoPeriod normalized() { 228 long monthRange = monthRange(); 229 if (monthRange > 0) { 230 long totalMonths = years * monthRange + months; 231 long splitYears = totalMonths / monthRange; 232 int splitMonths = (int) (totalMonths % monthRange); // no overflow 233 if (splitYears == years && splitMonths == months) { 234 return this; 235 } 236 return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days); 237 238 } 239 return this; 240 } 241 242 /** 243 * Calculates the range of months. 244 * 245 * @return the month range, -1 if not fixed range 246 */ 247 private long monthRange() { 248 ValueRange startRange = chrono.range(MONTH_OF_YEAR); 249 if (startRange.isFixed() && startRange.isIntValue()) { 250 return startRange.getMaximum() - startRange.getMinimum() + 1; 251 } 252 return -1; 253 } 254 255 //------------------------------------------------------------------------- 256 @Override 257 public Temporal addTo(Temporal temporal) { 258 validateChrono(temporal); 259 if (months == 0) { 260 if (years != 0) { 261 temporal = temporal.plus(years, YEARS); 262 } 263 } else { 264 long monthRange = monthRange(); 265 if (monthRange > 0) { 266 temporal = temporal.plus(years * monthRange + months, MONTHS); 267 } else { 268 if (years != 0) { 269 temporal = temporal.plus(years, YEARS); 270 } 271 temporal = temporal.plus(months, MONTHS); 272 } 273 } 274 if (days != 0) { 275 temporal = temporal.plus(days, DAYS); 276 } 277 return temporal; 278 } 279 280 281 282 @Override 283 public Temporal subtractFrom(Temporal temporal) { 284 validateChrono(temporal); 285 if (months == 0) { 286 if (years != 0) { 287 temporal = temporal.minus(years, YEARS); 288 } 289 } else { 290 long monthRange = monthRange(); 291 if (monthRange > 0) { 292 temporal = temporal.minus(years * monthRange + months, MONTHS); 293 } else { 294 if (years != 0) { 295 temporal = temporal.minus(years, YEARS); 296 } 297 temporal = temporal.minus(months, MONTHS); 298 } 299 } 300 if (days != 0) { 301 temporal = temporal.minus(days, DAYS); 302 } 303 return temporal; 304 } 305 306 /** 307 * Validates that the temporal has the correct chronology. 308 */ 309 private void validateChrono(TemporalAccessor temporal) { 310 Objects.requireNonNull(temporal, "temporal"); 311 Chronology temporalChrono = temporal.query(TemporalQueries.chronology()); 312 if (temporalChrono != null && chrono.equals(temporalChrono) == false) { 313 throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId()); 314 } 315 } 316 317 //----------------------------------------------------------------------- 318 @Override 319 public boolean equals(Object obj) { 320 if (this == obj) { 321 return true; 322 } 323 if (obj instanceof ChronoPeriodImpl) { 324 ChronoPeriodImpl other = (ChronoPeriodImpl) obj; 325 return years == other.years && months == other.months && 326 days == other.days && chrono.equals(other.chrono); 327 } 328 return false; 329 } 330 331 @Override 332 public int hashCode() { 333 return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode(); 334 } 335 336 //----------------------------------------------------------------------- 337 @Override 338 public String toString() { 339 if (isZero()) { 340 return getChronology().toString() + " P0D"; 341 } else { 342 StringBuilder buf = new StringBuilder(); 343 buf.append(getChronology().toString()).append(' ').append('P'); 344 if (years != 0) { 345 buf.append(years).append('Y'); 346 } 347 if (months != 0) { 348 buf.append(months).append('M'); 349 } 350 if (days != 0) { 351 buf.append(days).append('D'); 352 } 353 return buf.toString(); 354 } 355 } 356 357 //----------------------------------------------------------------------- 358 /** 359 * Writes the Chronology using a 360 * <a href="{@docRoot}/serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. 361 * <pre> 362 * out.writeByte(12); // identifies this as a ChronoPeriodImpl 363 * out.writeUTF(getId()); // the chronology 364 * out.writeInt(years); 365 * out.writeInt(months); 366 * out.writeInt(days); 367 * </pre> 368 * 369 * @return the instance of {@code Ser}, not null 370 */ 371 @java.io.Serial 372 protected Object writeReplace() { 373 return new Ser(Ser.CHRONO_PERIOD_TYPE, this); 374 } 375 376 /** 377 * Defend against malicious streams. 378 * 379 * @param s the stream to read 380 * @throws InvalidObjectException always 381 */ 382 @java.io.Serial 383 private void readObject(ObjectInputStream s) throws ObjectStreamException { 384 throw new InvalidObjectException("Deserialization via serialization delegate"); 385 } 386 387 void writeExternal(DataOutput out) throws IOException { 388 out.writeUTF(chrono.getId()); 389 out.writeInt(years); 390 out.writeInt(months); 391 out.writeInt(days); 392 } 393 394 static ChronoPeriodImpl readExternal(DataInput in) throws IOException { 395 Chronology chrono = Chronology.of(in.readUTF()); 396 int years = in.readInt(); 397 int months = in.readInt(); 398 int days = in.readInt(); 399 return new ChronoPeriodImpl(chrono, years, months, days); 400 } 401 402 }