/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (SingleUnitTimeSpan.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.range; import net.time4j.Duration; import net.time4j.IsoDateUnit; import net.time4j.base.MathUtils; import net.time4j.engine.TimePoint; import net.time4j.engine.TimeSpan; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.text.ParseException; import java.util.Collections; import java.util.List; /** * <p>Represents a time span in one calendrical unit only. </p> * * @param <U> generic type of calendrical units * @author Meno Hochschild * @since 3.21/4.17 */ /*[deutsch] * <p>Repräsentiert eine Zeitspanne in nur einer kalendarischen Zeiteinheit. </p> * * @param <U> generic type of calendrical units * @author Meno Hochschild * @since 3.21/4.17 */ public abstract class SingleUnitTimeSpan<U extends IsoDateUnit, D extends SingleUnitTimeSpan<U, D>> implements TimeSpan<U>, Comparable<D>, Serializable { //~ Instanzvariablen -------------------------------------------------- /** * @serial count of units */ /*[deutsch] * @serial Anzahl der Zeiteinheiten */ private final int amount; /** * @serial type of unit */ /*[deutsch] * @serial Einheitstyp */ private final U unit; //~ Konstruktoren ----------------------------------------------------- // package-private SingleUnitTimeSpan( int amount, U unit ) { super(); this.amount = amount; this.unit = unit; this.checkConsistency(unit); } //~ Methoden ---------------------------------------------------------- /** * <p>Yields the count of units as integer-based amount. </p> * * @return int */ /*[deutsch] * <p>Liefert die Anzahl der Zeiteinheiten als Integer-Betrag. </p> * * @return int */ public int getAmount() { return this.amount; } /** * <p>Yields the associated unit. </p> * * @return calendrical unit */ /*[deutsch] * <p>Liefert die assoziierte Zeiteinheit. </p> * * @return calendrical unit */ public U getUnit() { return this.unit; } @Override public int compareTo(D other) { if (this.unit.equals(other.getUnit())) { return ((this.amount < other.getAmount()) ? -1 : (this.amount == other.getAmount()) ? 0 : 1); } else { throw new ClassCastException("Durations with different units are not comparable."); } } @Override public List<Item<U>> getTotalLength() { if (this.isEmpty()) { return Collections.emptyList(); } long value = this.amount; // prevents anomaly for Integer.MIN_VALUE Item<U> item = Item.of(Math.abs(value), this.unit); return Collections.singletonList(item); } @Override public boolean contains(IsoDateUnit unit) { return (this.unit.equals(unit) && (this.amount != 0)); } @Override public long getPartialAmount(IsoDateUnit unit) { return (this.unit.equals(unit) ? this.amount : 0); } @Override public boolean isNegative() { return (this.amount < 0); } @Override public boolean isPositive() { return (this.amount > 0); } @Override public boolean isEmpty() { return (this.amount == 0); } @Override public <T extends TimePoint<? super U, T>> T addTo(T time) { return time.plus(this.amount, this.unit); } @Override public <T extends TimePoint<? super U, T>> T subtractFrom(T time) { return time.minus(this.amount, this.unit); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof SingleUnitTimeSpan) { SingleUnitTimeSpan<?, ?> that = SingleUnitTimeSpan.class.cast(obj); return ((this.amount == that.amount) && this.unit.equals(that.unit)); } else { return false; } } @Override public int hashCode() { return (this.amount ^ this.unit.hashCode()); } /** * <p>Prints in ISO-8601-format "PnU" (n=amount, U=unit). </p> * * <p>Negative durations will get a preceding sign before "P". Note that gregorian * and week-based years have the same representation using the symbol Y. </p> * * @return the duration in ISO-8601-format */ /*[deutsch] * <p>Liefert einen ISO-8601-kompatiblen String im Format "PnU" (n=Betrag, U=Einheit). </p> * * <p>Eine negative Dauer bekommt ein Minuszeichen vorangestellt. Zu beachten: Gregorianische und * wochenbasierte Jahre haben die gleiche Repräsentation mit dem Symbol Y. </p> * * @return the duration in ISO-8601-format */ @Override public String toString() { StringBuilder sb = new StringBuilder(); if (this.amount < 0) { sb.append('-'); } sb.append('P'); long value = this.amount; // prevents anomaly for Integer.MIN_VALUE sb.append(Math.abs(value)); sb.append(this.unit.getSymbol()); return sb.toString(); } /** * <p>Yields a copy with the absolute amount. </p> * * @return immutable copy with the absolute amount * @throws ArithmeticException if numeric overflow occurs (only in case of {@code Integer.MIN_VALUE}) */ /*[deutsch] * <p>Liefert eine Kopie mit dem absoluten Betrag. </p> * * @return immutable copy with the absolute amount * @throws ArithmeticException if numeric overflow occurs (only in case of {@code Integer.MIN_VALUE}) */ public D abs() { long value = this.amount; return this.with(MathUtils.safeCast(Math.abs(value))); } /** * <p>Yields a copy with the negated amount. </p> * * @return immutable copy with the reverse sign * @throws ArithmeticException if numeric overflow occurs (only in case of {@code Integer.MIN_VALUE}) */ /*[deutsch] * <p>Liefert eine Kopie mit dem negierten Betrag. </p> * * @return immutable copy with the reverse sign * @throws ArithmeticException if numeric overflow occurs (only in case of {@code Integer.MIN_VALUE}) */ public D inverse() { return this.with(MathUtils.safeNegate(this.amount)); } /** * <p>Yields a copy with the added amount. </p> * * @param amount the amount to be added * @return result of addition as immutable copy * @throws ArithmeticException if numeric overflow occurs */ /*[deutsch] * <p>Liefert eine Kopie mit dem addierten Betrag. </p> * * @param amount the amount to be added * @return result of addition as immutable copy * @throws ArithmeticException if numeric overflow occurs */ public D plus(int amount) { if (amount == 0) { return this.self(); } long value = this.amount; return this.with(MathUtils.safeCast(value + amount)); } /** * <p>Yields a copy with the added duration. </p> * * @param duration the duration to be added * @return result of addition as immutable copy * @throws ArithmeticException if numeric overflow occurs */ /*[deutsch] * <p>Liefert eine Kopie mit der addierten Dauer. </p> * * @param duration the duration to be added * @return result of addition as immutable copy * @throws ArithmeticException if numeric overflow occurs */ public D plus(D duration) { if (duration.isEmpty()) { return this.self(); } long value = this.amount; return this.with(MathUtils.safeCast(value + duration.getAmount())); } /** * <p>Yields a copy with the subtracted amount. </p> * * @param amount the amount to be subtracted * @return result of subtraction as immutable copy * @throws ArithmeticException if numeric overflow occurs */ /*[deutsch] * <p>Liefert eine Kopie mit dem subtrahierten Betrag. </p> * * @param amount the amount to be subtracted * @return result of subtraction as immutable copy * @throws ArithmeticException if numeric overflow occurs */ public D minus(int amount) { if (amount == 0) { return this.self(); } long value = this.amount; return this.with(MathUtils.safeCast(value - amount)); } /** * <p>Yields a copy with the subtracted duration. </p> * * @param duration the duration to be added * @return result of subtraction as immutable copy * @throws ArithmeticException if numeric overflow occurs */ /*[deutsch] * <p>Liefert eine Kopie mit der subtrahierten Dauer. </p> * * @param duration the duration to be added * @return result of subtraction as immutable copy * @throws ArithmeticException if numeric overflow occurs */ public D minus(D duration) { if (duration.isEmpty()) { return this.self(); } long value = this.amount; return this.with(MathUtils.safeCast(value - duration.getAmount())); } /** * <p>Yields a copy with the multiplied amount. </p> * * @param factor multiplication factor to be applied * @return immutable copy with the multiplied amount * @throws ArithmeticException if numeric overflow occurs */ /*[deutsch] * <p>Liefert eine Kopie mit dem multiplizierten Betrag. </p> * * @param factor multiplication factor to be applied * @return immutable copy with the multiplied amount * @throws ArithmeticException if numeric overflow occurs */ public D multipliedBy(int factor) { switch (factor) { case -1: return this.inverse(); case 1: return this.self(); default: return this.with(MathUtils.safeMultiply(this.amount, factor)); } } /** * <p>Converts this instance to a general duration with the same amount and unit. </p> * * @return Duration */ /*[deutsch] * <p>Konvertiert diese Instanz zu einer allgemeinen Dauer mit demselben Betrag und derselben Einheit. </p> * * @return Duration */ public Duration<U> toStdDuration() { return Duration.of(this.amount, this.unit); } // package-private abstract D with(int amount); // package-private abstract D self(); // called by subclasses static int parsePeriod( String period, char symbol ) throws ParseException { if (period.isEmpty()) { throw new ParseException("Empty period.", 0); } boolean negative = false; int index = 0; int len = period.length(); if (period.charAt(0) == '-') { index++; negative = true; } if ((index < len) && (period.charAt(index) != 'P')) { throw new ParseException("Missing P-literal: " + period, index); } index++; long total = 0; int old = index; for (int i = index, n = Math.min(len, 10); i < n; i++) { char c = period.charAt(i); if ((c >= '0') && (c <= '9')) { int digit = (c - '0'); total = total * 10 + digit; index++; } else { break; } } if (index == old) { throw new ParseException("Missing digits: " + period, index); } if ((len == index + 1) && (period.charAt(index) == symbol)) { index++; // consume unit symbol try { if (negative) { total = MathUtils.safeNegate(total); } return MathUtils.safeCast(total); } catch (ArithmeticException ae) { throw new ParseException(ae.getMessage(), index); } } throw new ParseException("Unparseable format: " + period, index); } /** * @serialData Checks the consistency * @param in object input stream * @throws InvalidObjectException in any case of inconsistencies */ final void readObject(ObjectInputStream in) throws IOException { this.checkConsistency(this.unit); } // used in deserialization and constructors void checkConsistency(U unit) { if (unit == null) { throw new NullPointerException("Missing unit."); } } }