1 /* 2 * Copyright (c) 2013, 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 private static final long serialVersionUID = 57387258289L; 102 103 /** 104 * The set of supported units. 105 */ 106 private static final List<TemporalUnit> SUPPORTED_UNITS = List.of(YEARS, MONTHS, DAYS); 107 108 /** 109 * The chronology. 110 */ 111 private final Chronology chrono; 112 /** 113 * The number of years. 114 */ 115 final int years; 116 /** 117 * The number of months. 118 */ 119 final int months; 120 /** 121 * The number of days. 122 */ 123 final int days; 124 125 /** 126 * Creates an instance. 127 */ 128 ChronoPeriodImpl(Chronology chrono, int years, int months, int days) { 129 Objects.requireNonNull(chrono, "chrono"); 130 this.chrono = chrono; 131 this.years = years; 132 this.months = months; 133 this.days = days; 134 } 135 136 //----------------------------------------------------------------------- 137 @Override 138 public long get(TemporalUnit unit) { 139 if (unit == ChronoUnit.YEARS) { 140 return years; 141 } else if (unit == ChronoUnit.MONTHS) { 142 return months; 143 } else if (unit == ChronoUnit.DAYS) { 144 return days; 145 } else { 146 throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); 147 } 148 } 149 150 @Override 151 public List<TemporalUnit> getUnits() { 152 return ChronoPeriodImpl.SUPPORTED_UNITS; 153 } 154 155 @Override 156 public Chronology getChronology() { 157 return chrono; 158 } 159 160 //----------------------------------------------------------------------- 161 @Override 162 public boolean isZero() { 163 return years == 0 && months == 0 && days == 0; 164 } 165 166 @Override 167 public boolean isNegative() { 168 return years < 0 || months < 0 || days < 0; 169 } 170 171 //----------------------------------------------------------------------- 172 @Override 173 public ChronoPeriod plus(TemporalAmount amountToAdd) { 174 ChronoPeriodImpl amount = validateAmount(amountToAdd); 175 return new ChronoPeriodImpl( 176 chrono, 177 Math.addExact(years, amount.years), 178 Math.addExact(months, amount.months), 179 Math.addExact(days, amount.days)); 180 } 181 182 @Override 183 public ChronoPeriod minus(TemporalAmount amountToSubtract) { 184 ChronoPeriodImpl amount = validateAmount(amountToSubtract); 185 return new ChronoPeriodImpl( 186 chrono, 187 Math.subtractExact(years, amount.years), 188 Math.subtractExact(months, amount.months), 189 Math.subtractExact(days, amount.days)); 190 } 191 192 /** 193 * Obtains an instance of {@code ChronoPeriodImpl} from a temporal amount. 194 * 195 * @param amount the temporal amount to convert, not null 196 * @return the period, not null 197 */ 198 private ChronoPeriodImpl validateAmount(TemporalAmount amount) { 199 Objects.requireNonNull(amount, "amount"); 200 if (amount instanceof ChronoPeriodImpl == false) { 201 throw new DateTimeException("Unable to obtain ChronoPeriod from TemporalAmount: " + amount.getClass()); 202 } 203 ChronoPeriodImpl period = (ChronoPeriodImpl) amount; 204 if (chrono.equals(period.getChronology()) == false) { 205 throw new ClassCastException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + period.getChronology().getId()); 206 } 207 return period; 208 } 209 210 //----------------------------------------------------------------------- 211 @Override 212 public ChronoPeriod multipliedBy(int scalar) { 213 if (this.isZero() || scalar == 1) { 214 return this; 215 } 216 return new ChronoPeriodImpl( 217 chrono, 218 Math.multiplyExact(years, scalar), 219 Math.multiplyExact(months, scalar), 220 Math.multiplyExact(days, scalar)); 221 } 222 223 //----------------------------------------------------------------------- 224 @Override 225 public ChronoPeriod normalized() { 226 long monthRange = monthRange(); 227 if (monthRange > 0) { 228 long totalMonths = years * monthRange + months; 229 long splitYears = totalMonths / monthRange; 230 int splitMonths = (int) (totalMonths % monthRange); // no overflow 231 if (splitYears == years && splitMonths == months) { 232 return this; 233 } 234 return new ChronoPeriodImpl(chrono, Math.toIntExact(splitYears), splitMonths, days); 235 236 } 237 return this; 238 } 239 240 /** 241 * Calculates the range of months. 242 * 243 * @return the month range, -1 if not fixed range 244 */ 245 private long monthRange() { 246 ValueRange startRange = chrono.range(MONTH_OF_YEAR); 247 if (startRange.isFixed() && startRange.isIntValue()) { 248 return startRange.getMaximum() - startRange.getMinimum() + 1; 249 } 250 return -1; 251 } 252 253 //------------------------------------------------------------------------- 254 @Override 255 public Temporal addTo(Temporal temporal) { 256 validateChrono(temporal); 257 if (months == 0) { 258 if (years != 0) { 259 temporal = temporal.plus(years, YEARS); 260 } 261 } else { 262 long monthRange = monthRange(); 263 if (monthRange > 0) { 264 temporal = temporal.plus(years * monthRange + months, MONTHS); 265 } else { 266 if (years != 0) { 267 temporal = temporal.plus(years, YEARS); 268 } 269 temporal = temporal.plus(months, MONTHS); 270 } 271 } 272 if (days != 0) { 273 temporal = temporal.plus(days, DAYS); 274 } 275 return temporal; 276 } 277 278 279 280 @Override 281 public Temporal subtractFrom(Temporal temporal) { 282 validateChrono(temporal); 283 if (months == 0) { 284 if (years != 0) { 285 temporal = temporal.minus(years, YEARS); 286 } 287 } else { 288 long monthRange = monthRange(); 289 if (monthRange > 0) { 290 temporal = temporal.minus(years * monthRange + months, MONTHS); 291 } else { 292 if (years != 0) { 293 temporal = temporal.minus(years, YEARS); 294 } 295 temporal = temporal.minus(months, MONTHS); 296 } 297 } 298 if (days != 0) { 299 temporal = temporal.minus(days, DAYS); 300 } 301 return temporal; 302 } 303 304 /** 305 * Validates that the temporal has the correct chronology. 306 */ 307 private void validateChrono(TemporalAccessor temporal) { 308 Objects.requireNonNull(temporal, "temporal"); 309 Chronology temporalChrono = temporal.query(TemporalQueries.chronology()); 310 if (temporalChrono != null && chrono.equals(temporalChrono) == false) { 311 throw new DateTimeException("Chronology mismatch, expected: " + chrono.getId() + ", actual: " + temporalChrono.getId()); 312 } 313 } 314 315 //----------------------------------------------------------------------- 316 @Override 317 public boolean equals(Object obj) { 318 if (this == obj) { 319 return true; 320 } 321 if (obj instanceof ChronoPeriodImpl) { 322 ChronoPeriodImpl other = (ChronoPeriodImpl) obj; 323 return years == other.years && months == other.months && 324 days == other.days && chrono.equals(other.chrono); 325 } 326 return false; 327 } 328 329 @Override 330 public int hashCode() { 331 return (years + Integer.rotateLeft(months, 8) + Integer.rotateLeft(days, 16)) ^ chrono.hashCode(); 332 } 333 334 //----------------------------------------------------------------------- 335 @Override 336 public String toString() { 337 if (isZero()) { 338 return getChronology().toString() + " P0D"; 339 } else { 340 StringBuilder buf = new StringBuilder(); 341 buf.append(getChronology().toString()).append(' ').append('P'); 342 if (years != 0) { 343 buf.append(years).append('Y'); 344 } 345 if (months != 0) { 346 buf.append(months).append('M'); 347 } 348 if (days != 0) { 349 buf.append(days).append('D'); 350 } 351 return buf.toString(); 352 } 353 } 354 355 //----------------------------------------------------------------------- 356 /** 357 * Writes the Chronology using a 358 * <a href="../../../serialized-form.html#java.time.chrono.Ser">dedicated serialized form</a>. 359 * <pre> 360 * out.writeByte(12); // identifies this as a ChronoPeriodImpl 361 * out.writeUTF(getId()); // the chronology 362 * out.writeInt(years); 363 * out.writeInt(months); 364 * out.writeInt(days); 365 * </pre> 366 * 367 * @return the instance of {@code Ser}, not null 368 */ 369 protected Object writeReplace() { 370 return new Ser(Ser.CHRONO_PERIOD_TYPE, this); 371 } 372 373 /** 374 * Defend against malicious streams. 375 * 376 * @param s the stream to read 377 * @throws InvalidObjectException always 378 */ 379 private void readObject(ObjectInputStream s) throws ObjectStreamException { 380 throw new InvalidObjectException("Deserialization via serialization delegate"); 381 } 382 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 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 }