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