001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.indriya.quantity.time;
031
032import static tech.units.indriya.unit.Units.DAY;
033import static tech.units.indriya.unit.Units.HOUR;
034import static tech.units.indriya.unit.Units.MINUTE;
035import static tech.units.indriya.unit.Units.SECOND;
036
037import java.time.Duration;
038import java.time.temporal.ChronoUnit;
039import java.time.temporal.TemporalAmount;
040import java.time.temporal.TemporalUnit;
041import java.util.Objects;
042import java.util.function.BinaryOperator;
043
044import javax.measure.Quantity;
045import javax.measure.Unit;
046import javax.measure.UnitConverter;
047import javax.measure.quantity.Frequency;
048import javax.measure.quantity.Time;
049
050import tech.units.indriya.AbstractQuantity;
051import tech.units.indriya.ComparableQuantity;
052import tech.units.indriya.function.Calculus;
053import tech.units.indriya.internal.function.calc.Calculator;
054import tech.units.indriya.quantity.Quantities;
055import tech.units.indriya.unit.Units;
056
057/**
058 * Class that represents {@link TemporalUnit} in Unit-API
059 * 
060 * @author Werner Keil
061 * @author Filip van Laenen
062 * @author Andi Huber
063 * @version 1.3, Jun 4, 2019
064 * @since 1.0
065 */
066public final class TemporalQuantity extends AbstractQuantity<Time> {
067  
068  private static final long serialVersionUID = -707159906206272775L;
069  
070  private final Object $lock1 = new Object[0]; // serializable lock for 'amount'
071  
072  private final TemporalUnit timeUnit;
073  private final Number value;
074  private transient TemporalAmount amount;
075
076  /**
077   * creates the {@link TemporalQuantity} using {@link TemporalUnit} and {@link Number}
078   * 
079   * @param timeUnit
080   *          - time to be used
081   * @param value
082   *          - value to be used
083   */
084  TemporalQuantity(Number value, TemporalUnit timeUnit) {
085    super(toUnit(timeUnit));
086    this.timeUnit = timeUnit;
087    this.value = value;
088  }
089
090  /**
091   * creates the {@link TemporalQuantity} using {@link TemporalUnit} and {@link Number}
092   * 
093   * @param value
094   *          - value to be used
095   * @param timeUnit
096   *          - time to be used
097   */
098  public static TemporalQuantity of(Number number, TemporalUnit timeUnit) {
099    return new TemporalQuantity(Objects.requireNonNull(number), Objects.requireNonNull(timeUnit));
100  }
101
102  /**
103   * Creates a {@link TemporalQuantity} based a {@link Quantity<Time>} converted to {@link Units#SECOND}.
104   * 
105   * @param quantity
106   *          - quantity to be used
107   * @return the {@link TemporalQuantity} converted be quantity in seconds.
108   */
109  public static TemporalQuantity of(Quantity<Time> quantity) {
110    Quantity<Time> seconds = Objects.requireNonNull(quantity).to(SECOND);
111    return new TemporalQuantity(seconds.getValue(), ChronoUnit.SECONDS);
112  }
113
114  /**
115   * Returns the {@link TemporalAmount} of this {@code TemporalQuantity}, which may involve rounding or truncation.
116   * 
117   * @return the TemporalAmount
118   * @throws ArithmeticException when the {@code value} of this {@code TemporalQuantity} cannot be converted to long
119   */
120  public TemporalAmount getTemporalAmount() {
121    synchronized ($lock1) {
122        if(amount==null) {
123            
124            long longValue = value.longValue();
125            
126            Number error = Calculator.of(value)
127            .subtract(longValue)
128            .abs()
129            .peek();
130
131            //TODO[220] we should try to switch to smaller units to minimize the error
132            if(Calculus.currentNumberSystem().compare(error, 1)>0) {
133                String msg = String.format("cannot round number %s to long", "" + value);
134                throw new ArithmeticException(msg);
135            }
136            amount = Duration.of(longValue, timeUnit);
137            
138        }
139    }
140    return amount;
141  }
142
143  /**
144   * get to {@link TemporalUnit}
145   * 
146   * @return the TemporalUnit
147   */
148  public TemporalUnit getTemporalUnit() {
149    return timeUnit;
150  }
151
152  /**
153   * get value expressed in {@link Number}
154   * 
155   * @return the value
156   */
157  public Number getValue() {
158    return value;
159  }
160
161  /**
162   * converts the {@link TemporalUnit} to {@link Unit}
163   * 
164   * @return the {@link TemporalQuantity#getTemporalUnit()} converted to Unit
165   */
166  public Unit<Time> toUnit() {
167    return toUnit(timeUnit);
168  }
169
170  /**
171   * Converts the {@link TemporalQuantity} to {@link Quantity<Time>}
172   * 
173   * @return this class converted to Quantity
174   */
175  public Quantity<Time> toQuantity() {
176    return Quantities.getQuantity(value, toUnit());
177  }
178
179  public TemporalQuantity to(TemporalUnit aTimeUnit) {
180    Quantity<Time> time = toQuantity().to(toUnit(aTimeUnit));
181    return new TemporalQuantity(time.getValue().longValue(), aTimeUnit);
182  }
183
184  private static Unit<Time> toUnit(TemporalUnit timeUnit) {
185    if (timeUnit instanceof ChronoUnit) {
186      ChronoUnit chronoUnit = (ChronoUnit) timeUnit;
187      switch (chronoUnit) {
188        case MICROS:
189          return TimeQuantities.MICROSECOND;
190        case MILLIS:
191          return TimeQuantities.MILLISECOND;
192        case NANOS:
193          return TimeQuantities.NANOSECOND;
194        case SECONDS:
195          return SECOND;
196        case MINUTES:
197          return MINUTE;
198        case HOURS:
199          return HOUR;
200        case DAYS:
201          return DAY;
202        default:
203          throw new IllegalArgumentException("TemporalQuantity only supports DAYS, HOURS, MICROS, MILLIS, MINUTES, NANOS, SECONDS ");
204      }
205    }
206    throw new IllegalArgumentException("TemporalQuantity only supports temporal units of type ChronoUnit");
207  }
208
209  @Override
210  public int hashCode() {
211    return Objects.hash(timeUnit, value);
212  }
213
214  @Override
215  public boolean equals(Object obj) {
216    if (this == obj) {
217      return true;
218    }
219    if (TemporalQuantity.class.isInstance(obj)) {
220      TemporalQuantity other = TemporalQuantity.class.cast(obj);
221      return Objects.equals(timeUnit, other.timeUnit) && Objects.equals(value, other.value);
222    }
223    if (obj instanceof Quantity<?>) {
224      Quantity<?> that = (Quantity<?>) obj;
225      return Objects.equals(getUnit(), that.getUnit()) && 
226              Calculus.currentNumberSystem().compare(value, that.getValue()) == 0;
227    }
228    return super.equals(obj);
229  }
230
231  @Override
232  public String toString() {
233    return "Temporal unit:" + timeUnit + " value: " + value;
234  }
235  
236  @Override
237  public ComparableQuantity<Time> add(Quantity<Time> that) {
238      
239      final UnitConverter thisToThat = this.getUnit().getConverterTo(that.getUnit());
240      final boolean thatUnitIsSmaller = 
241              Calculus.currentNumberSystem().compare(thisToThat.convert(1.), 1.)>0;
242
243      final Unit<Time> preferedUnit = thatUnitIsSmaller ? that.getUnit() : this.getUnit();
244      
245      final Number thisValueInPreferedUnit = convertedQuantityValue(this, preferedUnit);
246      final Number thatValueInPreferedUnit = convertedQuantityValue(that, preferedUnit);
247      
248      final Number resultValueInPreferedUnit = Calculator.of(thisValueInPreferedUnit)
249              .add(thatValueInPreferedUnit)
250              .peek();
251      
252      return Quantities.getQuantity(resultValueInPreferedUnit, preferedUnit);
253  }
254
255  @Override
256  public ComparableQuantity<Time> subtract(Quantity<Time> that) {
257    return add(that.negate());
258  }
259
260  @Override
261  public ComparableQuantity<?> divide(Quantity<?> that) {
262    return applyMultiplicativeQuantityOperation(
263            that, (a, b)->Calculator.of(a).divide(b).peek(), Unit::divide);
264  }
265
266  @Override
267  public ComparableQuantity<Time> divide(Number that) {
268    return applyMultiplicativeNumberOperation(
269            that, (a, b)->Calculator.of(a).divide(b).peek());
270  }
271
272  @Override
273  public ComparableQuantity<?> multiply(Quantity<?> that) {
274    return applyMultiplicativeQuantityOperation(
275            that, (a, b)->Calculator.of(a).multiply(b).peek(), Unit::multiply);
276  }
277
278  @Override
279  public ComparableQuantity<Time> multiply(Number that) {
280    return applyMultiplicativeNumberOperation(
281            that, (a, b)->Calculator.of(a).multiply(b).peek());
282  }
283
284  @Override
285  public ComparableQuantity<Frequency> inverse() {
286    return Quantities.getQuantity(
287            Calculator.of(value).reciprocal().peek(),
288            toUnit(timeUnit).inverse()).asType(Frequency.class);
289  }
290
291  /**
292   * @since 1.0.2
293   */
294  @Override
295  public Quantity<Time> negate() {
296    return of(Calculator.of(value).negate().peek(), getTemporalUnit());
297  }
298  
299  // -- HELPER
300  
301  private static <R extends Quantity<R>> Number quantityValue(Quantity<R> that) {
302      return convertedQuantityValue(that, that.getUnit());
303  }
304
305  private static <R extends Quantity<R>> Number convertedQuantityValue(Quantity<R> that, Unit<R> unit) {
306      return that.getUnit().getConverterTo(unit).convert(that.getValue());
307  }
308
309  private ComparableQuantity<?> applyMultiplicativeQuantityOperation(
310          Quantity<?> that,
311          BinaryOperator<Number> valueOperator,
312          BinaryOperator<Unit<?>> unitOperator) {
313
314      final Number thisValue = quantityValue(this);
315      final Number thatValue = quantityValue(that);
316      final Number result = valueOperator.apply(thisValue, thatValue);
317      final Unit<?> resultUnit = unitOperator.apply(getUnit(), that.getUnit());
318      return Quantities.getQuantity(result, resultUnit);
319  }
320
321  private ComparableQuantity<Time> applyMultiplicativeNumberOperation(Number that,
322          BinaryOperator<Number> valueOperator) {
323      final Number thisValue = this.getValue();
324      final Number thatValue = that;
325      final Number result = valueOperator.apply(thisValue, thatValue);
326      return Quantities.getQuantity(result, getUnit());
327  }
328  
329}