io.coala.time.TimeSpan.java Source code

Java tutorial

Introduction

Here is the source code for io.coala.time.TimeSpan.java

Source

/* $Id: 2495ecffe67831a63b81d014aaba7c3cb4e94338 $
 * 
 * @license
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.coala.time;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAmount;

import javax.measure.DecimalMeasure;
import javax.measure.Measurable;
import javax.measure.Measure;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Duration;
import javax.measure.quantity.Quantity;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;

import org.joda.time.Period;
import org.joda.time.ReadableDuration;
import org.jscience.physics.amount.Amount;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import io.coala.exception.Thrower;
import io.coala.log.LogUtil;
import io.coala.log.LogUtil.Pretty;
import io.coala.math.MeasureUtil;
import io.coala.util.DecimalUtil;

/**
 * {@link TimeSpan} extends {@link DecimalMeasure} with {@link #valueOf(String)}
 * for {@link Converters#CLASS_WITH_VALUE_OF_METHOD}.
 * <p>
 * Assumes {@linkplain Double#NaN} as value for illegal/empty value types
 * 
 * Considered but rejected usingJScience {@link Amount} as super type rather
 * than {@link DecimalMeasure}, providing (exact) arithmetic operations by
 * default, e.g. {@link Amount#plus(Amount)}, a la
 * <a href="https://www.jcp.org/en/jsr/detail?id=363">JSR 363</a> (see
 * <a href="https://github.com/unitsofmeasurement/uom-se">Java8 reference
 * implementation</a>) due to {@link Amount}s incompatibility with
 * {@link BigDecimal} values
 * 
 * @version $Id: 2495ecffe67831a63b81d014aaba7c3cb4e94338 $
 * @author Rick van Krevelen
 */
@SuppressWarnings("rawtypes")
@JsonSerialize(using = TimeSpan.ToJsonString.class)
@JsonDeserialize(using = TimeSpan.FromJsonValue.class)
public class TimeSpan extends DecimalMeasure {
    static {
        Units.registerAliases();
    }

    /** */
    private static final long serialVersionUID = 1L;

    /**
     * for "natural" Config value conversion for a {@link Duration} (i.e.
     * {@link TimeSpan}).
     * 
     * @param value a duration as {@link DecimalMeasure JSR-275} measure (e.g.
     *            {@code "123 ms"}) or as ISO Period, parsed with
     *            {@link org.threeten.bp.Duration#parse(CharSequence) JSR-310}
     *            or {@link Period#parse(String) Joda}.
     * 
     *            Examples of ISO period:
     * 
     *            <pre>
     *    "PT20.345S" -> parses as "20.345 seconds"
     *    "PT15M"     -> parses as "15 minutes" (where a minute is 60 seconds)
     *    "PT10H"     -> parses as "10 hours" (where an hour is 3600 seconds)
     *    "P2D"       -> parses as "2 days" (where a day is 24 hours or 86400 seconds)
     *    "P2DT3H4M"  -> parses as "2 days, 3 hours and 4 minutes"
     *    "P-6H3M"    -> parses as "-6 hours and +3 minutes"
     *    "-P6H3M"    -> parses as "-6 hours and -3 minutes"
     *    "-P-6H+3M"  -> parses as "+6 hours and -3 minutes"
     *            </pre>
     * 
     * @see org.aeonbits.owner.Converters.CLASS_WITH_VALUE_OF_METHOD
     * @see java.time.Duration#parse(String)
     * @see org.joda.time.format.ISOPeriodFormat#standard()
     * @see DecimalMeasure
     */
    public static TimeSpan valueOf(final String value) {
        return of(value);
    }

    public static TimeSpan of(final String value) {
        return new TimeSpan(value);
    }

    /**
     * {@link TimeSpan} static factory method
     * 
     * @param temporal a JSR-310 {@link TemporalAmount}
     */
    public static TimeSpan of(final TemporalAmount temporal) {
        return new TimeSpan(
                BigDecimal.valueOf(temporal.get(ChronoUnit.NANOS))
                        .add(BigDecimal.valueOf(temporal.get(ChronoUnit.MILLIS)).multiply(BigDecimal.TEN.pow(6))),
                Units.NANOS);
    }

    /**
     * {@link TimeSpan} static factory method
     * 
     * @param value a Joda {@link ReadableDuration}, e.g.
     *            {@link org.joda.time.Duration}
     */
    public static TimeSpan of(final ReadableDuration value) {
        return new TimeSpan(BigDecimal.valueOf(value.getMillis()), Units.MILLIS);
    }

    /**
     * {@link TimeSpan} static factory method
     * 
     * @param value a {@link Measure} of some {@link Duration} or
     *            {@link Dimensionless} {@link Quantity}
     */
    public static TimeSpan of(final Measure<?, ?> value) {
        return value instanceof TimeSpan ? (TimeSpan) value : new TimeSpan(value);
    }

    /**
     * {@link TimeSpan} static factory method
     * 
     * @param value an {@link Amount} of {@link Duration} or
     *            {@link Dimensionless} units
     */
    public static TimeSpan of(final Amount value) {
        return new TimeSpan(value);
    }

    /**
     * {@link TimeSpan} static factory method
     * 
     * @param units the amount of time units
     * @return a {@link Dimensionless} {@link TimeSpan}
     */
    public static TimeSpan of(final Number units) {
        return of(units, Unit.ONE);
    }

    /**
     * Returns the decimal measure for the specified number stated in the
     * specified unit.
     * 
     * @param decimal the measurement value
     * @param unit the measurement {@link Unit}
     * @see DecimalMeasure#valueOf(Number, Unit)
     */
    public static TimeSpan of(final Number decimal, final Unit<?> unit) {
        return new TimeSpan(decimal, unit);
    }

    /** */
    // private static final Logger LOG = LogManager.getLogger(TimeSpan.class);

    /** the ZERO */
    public static final TimeSpan ZERO = of(0, Unit.ONE);

    /** the ONE */
    public static final TimeSpan ONE = of(1, Unit.ONE);

    /**
     * Parse duration as {@link DecimalMeasure JSR-275} measure (e.g.
     * {@code "123 ms"}) or as ISO Period with
     * {@link org.threeten.bp.Duration#parse(CharSequence) JSR-310} or
     * {@link Period#parse(String) Joda}.
     * 
     * Examples of ISO period:
     * 
     * <pre>
     *    "PT20.345S" -> parses as "20.345 seconds"
     *    "PT15M"     -> parses as "15 minutes" (where a minute is 60 seconds)
     *    "PT10H"     -> parses as "10 hours" (where an hour is 3600 seconds)
     *    "P2D"       -> parses as "2 days" (where a day is 24 hours or 86400 seconds)
     *    "P2DT3H4M"  -> parses as "2 days, 3 hours and 4 minutes"
     *    "P-6H3M"    -> parses as "-6 hours and +3 minutes"
     *    "-P6H3M"    -> parses as "-6 hours and -3 minutes"
     *    "-P-6H+3M"  -> parses as "+6 hours and -3 minutes"
     * </pre>
     * 
     * @param measure the {@link String} representation of a duration
     * @return
     * 
     * @see org.threeten.bp.Duration#parse(String)
     * @see org.joda.time.format.ISOPeriodFormat#standard()
     * @see DecimalMeasure
     */
    protected static final Measure<BigDecimal, Duration> parsePeriodOrMeasure(final String measure) {
        if (measure == null)
            return null;//throw new NullPointerException();
        DecimalMeasure<Duration> result;
        try {
            result = DecimalMeasure.valueOf(measure + " ");
            // LOG.trace("Parsed '{}' as JSR-275 measure/unit: {}", measure,
            // result);
            return result;
        } catch (final Exception a) {
            // LOG.trace("JSR-275 failed, try JSR-310", e);
            try {
                // final long millis = Period.parse(measure).getMillis();
                // return DecimalMeasure.valueOf(BigDecimal.valueOf(millis),
                // SI.MILLI(SI.SECOND));
                final java.time.Duration temp = java.time.Duration.parse(measure);
                result = temp.getNano() == 0
                        ? DecimalMeasure.valueOf(BigDecimal.valueOf(temp.getSeconds()), SI.SECOND)
                        : DecimalMeasure.valueOf(BigDecimal.valueOf(temp.getSeconds())
                                .multiply(BigDecimal.TEN.pow(9)).add(BigDecimal.valueOf(temp.getNano())),
                                Units.NANOS);
                // LOG.trace(
                // "Parsed '{}' using JSR-310 to JSR-275 measure/unit: {}",
                // measure, result);
                return result;
            } catch (final Exception e) {
                // LOG.trace("JSR-275 and JSR-310 failed, try Joda", e);
                try {
                    final Period joda = Period.parse(measure);
                    result = DecimalMeasure.valueOf(BigDecimal.valueOf(joda.toStandardDuration().getMillis()),
                            Units.MILLIS);
                    // LOG.trace(
                    // "Parsed '{}' using Joda to JSR-275 measure/unit: {}",
                    // measure, result);
                    return result;
                } catch (final Exception j) {
                    return Thrower.throwNew(IllegalArgumentException.class,
                            "Could not parse duration or period from: {}"
                                    + ", JSR-275: {}, JSR-310: {}, Joda-time: {}",
                            measure, a.getMessage(), e.getMessage(), j.getMessage());
                }
            }
        }
    }

    /**
     * {@link TimeSpan} constructor for "natural" polymorphic Jackson bean
     * deserialization
     * 
     * @see com.fasterxml.jackson.databind.deser.BeanDeserializer
     */
    public TimeSpan(final String measure) {
        this(parsePeriodOrMeasure(measure));
    }

    /**
     * {@link TimeSpan} constructor for "natural" polymorphic Jackson bean
     * deserialization
     * 
     * @see com.fasterxml.jackson.databind.deser.BeanDeserializer
     */
    public TimeSpan(final double millis) {
        this(millis, Unit.ONE);
    }

    /**
     * {@link TimeSpan} constructor for "natural" polymorphic Jackson bean
     * deserialization
     * 
     * @see com.fasterxml.jackson.databind.deser.BeanDeserializer
     */
    public TimeSpan(final int millis) {
        this(millis, Unit.ONE);
    }

    /**
     * {@link TimeSpan} constructor
     * 
     * @param measure
     * @param unit
     */
    public <Q extends Quantity> TimeSpan(final Measure<?, Q> measure) {
        this(MeasureUtil.toBigDecimal(measure), measure.getUnit());
    }

    /**
     * {@link TimeSpan} constructor
     * 
     * @param amount
     * @param unit
     */
    public TimeSpan(final Amount<?> amount) {
        this(MeasureUtil.toBigDecimal(amount), amount.getUnit());
    }

    /**
     * {@link TimeSpan} main constructor for (inexact) {@link Number}s
     * 
     * @param value
     * @param unit
     */
    @SuppressWarnings("unchecked")
    public TimeSpan(final Number value, final Unit unit) {
        super(DecimalUtil.valueOf(value), unit);
    }

    //   @Override
    //   public int compareTo( final Measurable that )
    //   {
    //      return MeasureUtil.compareTo( this, that );
    //   }

    /**
     * @param augend the {@link Measurable}, e.g. another {@link TimeSpan},
     *            {@link Measure} or {@link Amount}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public TimeSpan add(final Measurable<?> augend) {
        return augend instanceof DecimalMeasure
                ? add(MeasureUtil.toUnit((DecimalMeasure) augend, getUnit()).getValue())
                : add(augend instanceof Amount && ((Amount) augend).isExact() ? augend.longValue(getUnit())
                        : augend.doubleValue(getUnit()));
    }

    /**
     * @param augend
     * @return a new {@link TimeSpan}
     */
    public TimeSpan add(final Number augend) {
        return of(getValue().add(DecimalUtil.valueOf(augend)), getUnit());
    }

    /**
     * @param subtrahend the {@link Measure}, e.g. another {@link TimeSpan}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public TimeSpan subtract(final Measurable<?> subtrahend) {
        return subtrahend instanceof DecimalMeasure
                ? subtract(MeasureUtil.toUnit((DecimalMeasure) subtrahend, getUnit()).getValue())
                : subtrahend instanceof Amount && ((Amount) subtrahend).isExact()
                        ? subtract(subtrahend.longValue(getUnit()))
                        : subtract(subtrahend.doubleValue(getUnit()));
    }

    /**
     * @param subtrahend
     * @return a new {@link TimeSpan}
     */
    public TimeSpan subtract(final Number subtrahend) {
        return of(getValue().subtract(DecimalUtil.valueOf(subtrahend)), getUnit());
    }

    /**
     * @param divisor the {@link Dimensionless} {@link Measure}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public TimeSpan divide(final Measurable<Dimensionless> divisor) {
        return divide(divisor instanceof DecimalMeasure
                ? MeasureUtil.toUnit((DecimalMeasure) divisor, Unit.ONE).getValue()
                : BigDecimal.valueOf(divisor.doubleValue(Unit.ONE)));
    }

    /**
     * @param divisor the {@link Measure}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public <Q extends Quantity> TimeSpan divide(final Measure<? extends Number, Q> divisor) {
        return of(getValue().divide(DecimalUtil.valueOf(divisor.getValue())), getUnit().divide(divisor.getUnit()));
    }

    /**
     * @param divisor the {@link Amount}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public <Q extends Quantity> TimeSpan divide(final Amount<Q> divisor) {
        return of(getValue().divide(MeasureUtil.toBigDecimal(divisor)), getUnit().divide(divisor.getUnit()));
    }

    /**
     * @param divisor
     * @return a new {@link TimeSpan}
     */
    public TimeSpan divide(final Number divisor) {
        return of(getValue().divide(DecimalUtil.valueOf(divisor), DecimalUtil.DEFAULT_CONTEXT), getUnit());
    }

    /**
     * @param multiplier the {@link Dimensionless} multiplier {@link Measure}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public TimeSpan multiply(final Measurable<Dimensionless> multiplier) {
        return multiply(multiplier instanceof DecimalMeasure
                ? MeasureUtil.toUnit((DecimalMeasure) multiplier, Unit.ONE).getValue()
                : BigDecimal.valueOf(multiplier.doubleValue(Unit.ONE)));
    }

    /**
     * @param multiplier the {@link Measure}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public <Q extends Quantity> TimeSpan multiply(final Measure<? extends Number, Q> multiplier) {
        return of(getValue().multiply(DecimalUtil.valueOf(multiplier.getValue())),
                getUnit().times(multiplier.getUnit()));
    }

    /**
     * @param multiplier the {@link Amount}
     * @return a new {@link TimeSpan}
     */
    @SuppressWarnings("unchecked")
    public <Q extends Quantity> TimeSpan multiply(final Amount<Q> multiplier) {
        return of(getValue().multiply(MeasureUtil.toBigDecimal(multiplier)), getUnit().times(multiplier.getUnit()));
    }

    /**
     * @param multiplicand the {@link Number} to multiply with
     * @return a new {@link TimeSpan}
     */
    public TimeSpan multiply(final Number multiplicand) {
        return of(getValue().multiply(DecimalUtil.valueOf(multiplicand), DecimalUtil.DEFAULT_CONTEXT), getUnit());
    }

    public Pretty prettify(final int scale) {
        return prettify(getUnit(), scale);
    }

    @SuppressWarnings("unchecked")
    public Pretty prettify(final Unit<?> unit, final int scale) {
        return LogUtil.wrapToString(
                () -> DecimalUtil.setScale(MeasureUtil.toUnit(this, unit).getValue(), scale).toString() + unit);
    }

    public static class ToJsonString extends JsonSerializer<TimeSpan> {
        @Override
        public void serialize(final TimeSpan value, final JsonGenerator gen, final SerializerProvider serializers)
                throws IOException, JsonProcessingException {
            gen.writeString(value.toString());
        }
    }

    public static class FromJsonValue extends JsonDeserializer<TimeSpan> {
        @Override
        public TimeSpan deserialize(final JsonParser p, final DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            if (p.getCurrentToken().isNumeric())
                return TimeSpan.of(p.getNumberValue());

            if (p.getCurrentToken().isScalarValue())
                return TimeSpan.of(p.getValueAsString());

            final TreeNode tree = p.readValueAsTree();
            if (tree.size() == 0)
                return null;

            return Thrower.throwNew(IllegalArgumentException.class, "Problem parsing {} from {}",
                    TimeSpan.class.getSimpleName(), tree);
        }
    }
}