package be.raildelays.delays; import javax.persistence.Embeddable; import java.io.Serializable; import java.time.*; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.time.temporal.UnsupportedTemporalTypeException; import java.util.Objects; /** * {@code TimeDelay} is an immutable object that represent a time plus its delay. * Out of it you can retrieve the {@code expectedTime} or the {@code effectiveTime}, * where the {@code effectiveTime} is the {@code expectedTime} plus a {@code delay}. * This is a {@code Value Object} class. * * @author Almex * @implSpec This class is immutable and thread-safe. * @since 2.0 * @implNote This class has a natural ordering that is inconsistent with equals. * Because {@code x.equals(y)} does not mean that {@code x.compareTo(y) == 0}. * <p> * Example: considering x=18:00+30" and y=18:30+00" {@link #equals(Object)} return {@code false} but * {@link #compareTo(TimeDelay)} return {@code true}. * </p> */ @Embeddable public final class TimeDelay implements Serializable, Comparable<TimeDelay> { private static final long DEFAULT_DELAY = 0L; private static final long serialVersionUID = -1026179811764044178L; private static final ChronoUnit DELAY_UNIT = ChronoUnit.MILLIS; private final LocalTime expectedTime; private final Long delay; // in number of milliseconds /** * Default constructor. * Build an {@code expectedTime} with current date and 0 delay. */ private TimeDelay() { this.expectedTime = LocalTime.now(); this.delay = DEFAULT_DELAY; } /** * Initialization constructor. * * @param expectedTime the expected time such as : 1st January 2000 at 12:00 * @param delay delay in milliseconds counting from the {@code expectedTime} */ private TimeDelay(LocalTime expectedTime, Long delay) { this.expectedTime = LocalTime.from(expectedTime); this.delay = delay != null ? delay : DEFAULT_DELAY; } /** * Create an an instance of {@link TimeDelay} with the current {@link LocalTime} for the expectedTime time and 0 delay. * * @return a non-null {@link TimeDelay} with the current {@link LocalTime} and 0 delay. */ public static TimeDelay now() { return new TimeDelay(); } /** * Create an an instance of {@link TimeDelay} with the {@code expectedTime} and 0 delay. * * @param expectedTime the expected time such as : 1st January 2000 at 12:00 * @return a {@link TimeDelay} with the {@code expectedTime} and 0 delay, * {@code null} if the {@code expectedTime} is {@code null}. */ public static TimeDelay of(LocalTime expectedTime) { return expectedTime != null ? new TimeDelay(expectedTime, DEFAULT_DELAY) : null; } /** * Create an an instance of {@link TimeDelay} with the {@code expectedTime} and the {@code delay}. * * @param expectedTime the expected time such as : 1st January 2000 at 12:00 * @param delay delay in milliseconds counting from the {@code expectedTime} * @return a {@link TimeDelay} with the {@code expectedTime} and the {@code delay}, * {@code null} if the {@code expectedTime} is {@code null}. */ public static TimeDelay of(LocalTime expectedTime, Long delay) { return expectedTime != null ? new TimeDelay(expectedTime, delay) : null; } /** * Create an an instance of {@link TimeDelay} with the {@code expectedTime} and the {@code delay} * of a certain {@code unit}. * * @param expectedTime the expected time such as : 1st January 2000 at 12:00 * @param delay delay in the {@code unit} counting from the {@code expectedTime} * @param unit unit of the delay (unsupported units are {@code ChronoUnit.MICROS}, {@code ChronoUnit.NANOS} * and all not supported by {@link LocalTime#isSupported(TemporalUnit)}) * @return a {@link TimeDelay} with the {@code expectedTime} and the {@code delay}, * {@code null} if the {@code expectedTime} is {@code null}. * @throws UnsupportedTemporalTypeException if the unit is not supported */ public static TimeDelay of(LocalTime expectedTime, Long delay, TemporalUnit unit) { TimeDelay result = null; Objects.requireNonNull(unit); if (expectedTime != null) { Duration duration = Duration.of(DEFAULT_DELAY, DELAY_UNIT); if (delay != null) { if (!unit.isSupportedBy(expectedTime) || ChronoUnit.NANOS.equals(unit) || ChronoUnit.MICROS.equals(unit)) { throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit); } duration = Duration.of(delay, unit); } result = new TimeDelay(expectedTime, duration.toMillis()); } return result; } /** * Create an an instance of {@link TimeDelay} from two {@link LocalTime} : * <ul> * <li>the expected time</li> * <li>the effective time</li> * </ul> * * @param expectedTime the expected time of this instance of {@link TimeDelay} * @param effectiveTime the effective time used to compute delay * @return a {@link TimeDelay} with as delay the duration between those two {@code expectedTime} * and {@code effectiveTime}, * {@code null} if the {@code expectedTime} is {@code null}. */ public static TimeDelay computeFrom(LocalTime expectedTime, LocalTime effectiveTime) { TimeDelay result = null; if (expectedTime != null) { Long delay = Delays.computeDelay(expectedTime, effectiveTime); result = new TimeDelay(expectedTime, delay); } return result; } /** * Returns a copy of the {@link TimeDelay} in parameter. * * @param timeDelay from which we copy the content * @return a non-null {@link TimeDelay} with the {@code expectedTime} and the {@code delay} of the argument. */ public static TimeDelay from(TimeDelay timeDelay) { return new TimeDelay(timeDelay.getExpectedTime(), timeDelay.getDelay()); } /** * Create an an instance of {@link TimeDelay} with the {@code expectedTime} given in parameter and 0 {@code delay}. * * @param expectedTime we take into account only the {@link #expectedTime} from this {@link TimeDelay} * @return a {@link TimeDelay} with the {@code expectedTime} and 0 {@code delay}, * {@code null} if the {@code expectedTime} is {@code null}. */ public static TimeDelay fromWithoutDelay(TimeDelay expectedTime) { return expectedTime != null ? new TimeDelay(expectedTime.getExpectedTime(), DEFAULT_DELAY) : null; } /** * Returns a copy of this {@link TimeDelay} for which we provide a new {@code delay}. * * @param delay delay in milliseconds counting from the {@code expectedTime} * @return a non-null {@link TimeDelay} with the {@code expectedTime} of this and the {@code delay}. */ public TimeDelay withDelay(Long delay) { return new TimeDelay(this.getExpectedTime(), delay); } /** * Compare if {@code this} {@link TimeDelay} is after the {@code target} {@link TimeDelay}. * * @param target the {@link TimeDelay} to compare with * @return {@code true} if {@code this} is after the{@code target}, {@code false} otherwise */ public boolean isAfter(TimeDelay target) { return compareTo(target) > 0; } /** * Compare if {@code this} {@link TimeDelay} is before the {@code target} {@link TimeDelay}. * * @param target the {@link TimeDelay} to compare with * @return {@code true} if {@code this} is before the{@code target}, {@code false} otherwise */ public boolean isBefore(TimeDelay target) { return compareTo(target) < 0; } /** * Translate a {@link TimeDelay} into a {@link LocalTime} in order to get the effective time of this * {@link TimeDelay}. * * @return a non-null {@link LocalTime} which is a combination of {@code expectedTime} * plus its {@code delay}. * @throws DateTimeException if the addition cannot be made * @throws ArithmeticException if numeric overflow occurs */ public LocalTime getEffectiveTime() { return expectedTime.plus(delay, DELAY_UNIT); } /** * Translate a {@link TimeDelay} into a {@link LocalDateTime}. * * @param date the date to combien with, not null * @return a non-null {@link LocalDateTime} which is a combination of {@code expectedTime} * plus its {@code delay} and the {@code date}. */ public LocalDateTime atDate(LocalDate date) { return getEffectiveTime().atDate(date); } @Override public String toString() { return expectedTime.toString() + " +" + delay + "ms"; } @Override public boolean equals(Object obj) { boolean result = false; if (obj == this) { result = true; } else if (obj instanceof TimeDelay) { TimeDelay target = (TimeDelay) obj; result = this.expectedTime.equals(target.expectedTime) && this.delay.equals(target.delay); } return result; } @Override public int hashCode() { int hash = 1; hash = hash * 7 + delay.hashCode(); hash = hash * 3 + expectedTime.hashCode(); return hash; } @Override public int compareTo(TimeDelay target) { Objects.requireNonNull(target); return this.getEffectiveTime().compareTo(target.getEffectiveTime()); } public final LocalTime getExpectedTime() { return expectedTime; } public final long getDelay() { return delay; } }