All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBusx.time.EInterval Maven / Gradle / Ivy

The newest version!
//
// Copyright 2021, 2022 Charles W. Rapp
//
// 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 net.sf.eBusx.time;

import java.time.Instant;
import java.util.Objects;
import net.sf.eBus.messages.EField;
import net.sf.eBus.util.Validator;

/**
 * Implements a time interval from one {@link Instant} to
 * another. {@code EInterval} guarantees that begin time is ≤
 * end time. Each end point may be separately configured to be
 * inclusive or exclusive. If begin time equals end time then
 * both ends must be marked as inclusive.
 * 

* {@code EInterval} extends {@link EField} so that it can be * used in an eBus message. This means that * {@link EInterval.Builder} must be used to create an * {@code EInterval} instance. *

*

* {@code EInterval} is immutable and thread-safe. *

*

Allen's Interval Algebra

*

* Allen's temporal reasoning calculus is based on 13 base * relations. Since 12 of these relations are simply inverses of * each other (for example the inverse of x precedes y * is y is preceded by x) only 7 relations are * implemented in {@code EInterval} which are: *

*
    *
  1. * precedes: * interval 1 precedes interval 2 if interval 1 end time < * interval 2 begin time. *
  2. *
  3. * meets: interval 1 * meets interval 2 if if interval 1 end time equals interval * 2 begin time. *
  4. *
  5. * overlaps: * interval 1 overlaps interval 2 if interval 1 begin time * < interval 2 begin time and interval 1 end time < * interval 2 end time. *
  6. *
  7. * contains: * interval 1 contains interval 2 if interval 1 begin time * < interval 2 begin time and interval 1 end time > * interval 2 end time. *
  8. *
  9. * starts: interval 1 * starts interval 2 if interval 1 begin time equals interval * 2 begin time and interval 1 end time < interval 2 end * time. *
  10. *
  11. * finishes: * interval 1 finishes interval 2 if interval 1 begin time * > interval 2 begin time and interval 1 end time equals * interval 2 end time. *
  12. *
  13. * equals: interval 1 * begin and end times equal interval 2 begin and end times. *
  14. *
*

* Note: interval algebra methods below are * based solely on the end point times and do not take * clusivity into account. *

* * @author Charles W. Rapp */ public final class EInterval extends EField { //--------------------------------------------------------------- // Member enums. // /** * Specifies whether an end point is inclusive or exclusive. */ public enum Clusivity { /** * The end point time is included in the interval. */ INCLUSIVE ('[', ']'), /** * The end point time is excluded from the interval. */ EXCLUSIVE ('(', ')'); //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Clusivity symbol for begin time. */ private final char mBeginSymbol; /** * Clusivity symbol for end time. */ private final char mEndSymbol; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates Clusivity enum instance. * @param beginSymbol begin time clusivity symbol. * @param endSymbol end time clusivity symbol. */ private Clusivity(final char beginSymbol, final char endSymbol) { mBeginSymbol = beginSymbol; mEndSymbol = endSymbol; } // end of Clusivity(char, char) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns clusivity begin symbol. * @return begin symbol. */ public char beginSymbol() { return (mBeginSymbol); } // end of beginSymbol() /** * Returns clusivity end symbol. * @return end symbol. */ public char endSymbol() { return (mEndSymbol); } // end of endSymbol() // // end of Get Methods. //------------------------------------------------------- } // end of enum Clusivity /** * Denotes if a timestamp denotes the past, current time, * future, or future on-going. */ public enum TimeLocation { /** * Feed begin or end time is in the past. */ PAST (true, false), /** * Feed begin or end time is the current time. Current * time is neither in the past nor the future. */ NOW (false, false), /** * Feed end time is in the future. Feed begin time may * not be in the future. */ FUTURE (false, true), /** * Feed end time continues into future indefinitely until * stopped. */ ON_GOING (false, true); //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Locals. // /** * Set to {@code true} if this historic subscription * references past notifications. */ private final boolean mIsPast; /** * Set to {@code true} if this historic subscription * references notifications in the future. */ private final boolean mIsFuture; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // private TimeLocation(final boolean pastFlag, final boolean futureFlag) { mIsPast = pastFlag; mIsFuture = futureFlag; } // end of TimeLocation(boolean, boolean) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get Methods. // /** * Returns {@code true} if time is located in past. * Note that {@link #NOW} is neither in the past or the * future. * @return {@code true} if time is located in past. */ public boolean isPast() { return (mIsPast); } // end of isPast() /** * Returns {@code true} if time is located in future. * Note that {@link #NOW} is neither in the past or the * future. * @return {@code true} if time is located in future. */ public boolean isFuture() { return (mIsFuture); } // end of isFuture() // // end of Get Methods. //------------------------------------------------------- } // end of enum TimeLocation //--------------------------------------------------------------- // Member data. // //----------------------------------------------------------- // Constants. // /** * Java serialization identifier. */ private static final long serialVersionUID = 0x00060100l; //----------------------------------------------------------- // Locals. // /** * Interval begins at this time location. This is needed when * for relative times like {@link TimeLocation#NOW} and * {@link TimeLocation#ON_GOING}. */ public final TimeLocation beginLocation; /** * Interval begin time. */ public final Instant beginTime; /** * Interval begin time clusivity. */ public final Clusivity beginClusivity; /** * Interval ends at this time location. This is needed * for relative times like {@link TimeLocation#NOW} and * {@link TimeLocation#ON_GOING}. */ public final TimeLocation endLocation; /** * Interval end time. */ public final Instant endTime; /** * Interval end time clusivity. */ public final Clusivity endClusivity; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new interval instance based on the builder * configuration. {@code Builder} guarantees that the * interval settings are correct and consistent. * @param builder contains interval settings. */ private EInterval(final Builder builder) { super (builder); this.beginLocation = builder.mBeginLocation; this.endLocation = builder.mEndLocation; this.beginTime = builder.mBeginTime; this.beginClusivity = builder.mBeginClusivity; this.endTime = builder.mEndTime; this.endClusivity = builder.mEndClusivity; } // end of EInterval() // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Object Method Overrides. // /** * Returns textual representation of end point times and * clusivities. Uses default {@code Instant} time format. * @return interval as text. */ @Override public String toString() { final StringBuilder retval = new StringBuilder(); retval.append(beginClusivity.beginSymbol()); if (beginLocation == TimeLocation.NOW) { retval.append("now"); } else { retval.append(beginTime); } retval.append(", "); if (null == endLocation) { retval.append(endTime); } else switch (endLocation) { case NOW: retval.append("now"); break; case ON_GOING: retval.append("on-going"); break; default: retval.append(endTime); break; } retval.append(endClusivity.endSymbol()); return (retval.toString()); } // end of toString() /** * equals: * Returns {@code true} if: *
    *
  • * {@code o} is the same {@code EInterval} instance as * {@code this} interval or *
  • *
  • * {@code o} is a non-{@code null EInterval} instance * with the same end points and clusivities as * {@code this EInterval} instance. *
  • *
* Otherwise returns {@code false}. * @param o comparison object. * @return {@code true} if {@code o} is a * non-{@code null EInterval} instance whose end points are * equal and have the same clusivities. */ @Override public boolean equals(final Object o) { boolean retcode = (this == o); if (!retcode && o instanceof EInterval) { final EInterval interval = (EInterval) o; retcode = (beginTime.equals(interval.beginTime) && beginClusivity == interval.beginClusivity && beginLocation == interval.beginLocation && endTime.equals(interval.endTime) && endClusivity == interval.endClusivity && endLocation == interval.endLocation); } return (retcode); } // end of equals(Object) /** * Returns hash code based on end point times and * clusivities. * @return hash code based on end point times and * clusivities. */ @Override public int hashCode() { return (Objects.hash(beginTime, beginClusivity, beginLocation, endTime, endClusivity, endLocation)); } // end of hashCode() // // end of Object Method Overrides. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * precedes: * Returns {@code true} if {@code this EInterval} end time is * < {@code interval} begin time. *

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if {@code this EInterval} precedes * {@code interval} and {@code false} otherwise. */ public boolean precedes(final EInterval interval) { return (endTime.compareTo(interval.beginTime) < 0); } // end of precedes(EInterval) /** * meets: * Returns {@code true} if {@code this EInterval} end time * equals {@code interval} begin time. *

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if {@code this EInterval} meets * {@code interval} and {@code false} otherwise. */ public boolean meets(final EInterval interval) { return (endTime.equals(interval.beginTime)); } // end of meets(EInterval) /** * overlaps: * Returns {@code true} if: *
    *
  • * {@code this EInterval} end time is > * {@code interval}'s begin time, *
  • *
  • * {@code this EInterval} end time is < * {@code interval}'s end time, and *
  • *
  • * {@code this EInterval} begin time is < * {@code interval}'s begin time. *
  • *
*

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if {@code this EInterval} overlaps * {@code interval} and {@code false} otherwise. */ public boolean overlaps(final EInterval interval) { return (endTime.compareTo(interval.beginTime) > 0 && endTime.compareTo(interval.endTime) < 0 && beginTime.compareTo(interval.beginTime) < 0); } // end of overlaps(EInterval) /** * contains: * Returns {@code true} if: *
    *
  • * {@code this EInterval} begin time < * {@code interval} begin time and *
  • *
  • * {@code this EInterval} end time > {@code interval} * end time. *
  • *
* In short, this interval contains the given argument * interval. *

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if this interval begins before * {@code interval} and ends after. */ public boolean contains(final EInterval interval) { return (beginTime.compareTo(interval.beginTime) < 0 && endTime.compareTo(interval.endTime) > 0); } // end of contains(EInterval) /** * starts: * Returns {@code true} if this interval starts at the same * time as {@code interval} but ends before. That is: *
    *
  • * {@code this EInterval} begin time equals * {@code interval} begin time and *
  • *
  • * {@code this EInterval} end time < {@code interval} * end time. *
  • *
*

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if this interval starts at the same * time as {@code interval} but ends before. */ public boolean starts(final EInterval interval) { return (beginTime.equals(interval.beginTime) && endTime.compareTo(interval.endTime) < 0); } // end of starts(EInterval) /** * finishes: * Returns {@code true} if this interval starts after * {@code intervaL} buts ends at the same time. That is: *
    *
  • * {@code this EInterval} begin time > * {@code interval} begin time and *
  • *
  • * {@code this EInterval} end time equals * {@code interval} end time. *
  • *
*

* Note that this comparison is based only on time and does * not take clusivity into account. *

* @param interval comparison time interval. * @return {@code true} if this interval starts after * {@code intervaL} buts ends at the same time. */ public boolean finishes(final EInterval interval) { return (beginTime.compareTo(interval.beginTime) > 0 && endTime.equals(interval.endTime)); } // end of finishes(EInterval) // // end of Get Methods. //----------------------------------------------------------- /** * Returns a new {@code EInterval} builder instance which is * used to create an {@code EInterval} object. It is * recommended that a new builder be used when creating an * {@code EInterval} instance and not re-use a previously * created {@code Builder} to create multiple intervals. * @return interval builder instance. */ public static Builder builder() { return (new Builder()); } // end of builder() //--------------------------------------------------------------- // Inner classes. // /** * {@code EInterval} instances may be created only by using * a {@code Builder} instance. A {@code Builder} instance is * obtained by calling {@link #builder()} which returns a * newly instantiated {@code Builder} instance. It is * recommended that a new {@code Builder} instance be used to * create an interval rather than re-using a builder instance * to create multiple intervals. */ public static final class Builder extends EField.Builder { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Constants. // private static final String BEGIN_TIME = "beginTime"; private static final String BEGIN_CLUSIVITY_NULL = "beginClusivity is null"; private static final String END_CLUSIVITY_NULL = "endClusivity is null"; //------------------------------------------------------- // Locals. // /** * Use when setting begin or end time to now. */ private final Instant mNow; private Instant mBeginTime; private Clusivity mBeginClusivity; private TimeLocation mBeginLocation; private Instant mEndTime; private Clusivity mEndClusivity; private TimeLocation mEndLocation; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Constructor is private because a Builder instance * may only be created by calling {@link #builder()}. */ private Builder() { super (EInterval.class); mNow = Instant.now(); } // end of Builder() // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Abstract Builder Overrides. // /** * Checks if: *
    *
  1. * the begin and end times and clusivity are set and *
  2. *
  3. * begin time is either < or ≤ end time * depending on clusivity settings. *
  4. *
  5. * if begin time equals end time then both times must * be inclusive. *
  6. *
* @param problems add each detected problem to this * validator. * @return {@code problems}. */ @Override @SuppressWarnings ({"java:S1067"}) protected Validator validate(final Validator problems) { return (problems.requireNotNull(mBeginTime, BEGIN_TIME) .requireNotNull(mBeginClusivity, "beginClusivity") .requireNotNull(mEndTime, "endTime") .requireNotNull(mEndClusivity, "endClusivity") .requireTrue(mBeginTime != null && mEndTime != null && mBeginTime.compareTo(mEndTime) <= 0, BEGIN_TIME, "beginTime > endTime") .requireTrue( (mBeginTime != null && mEndTime != null && (!mBeginTime.equals(mEndTime) || (mBeginClusivity == Clusivity.INCLUSIVE || mEndClusivity == Clusivity.INCLUSIVE))), BEGIN_TIME, "beginTime == endTime and both ends are not inclusive")); } // end of validate(Validator) /** * Returns a new interval instance based on this * builder's settings. * @return new interval instance. */ @Override protected EInterval buildImpl() { return (new EInterval(this)); } // end of buildImpl() // // end of Abstract Builder Overrides. //------------------------------------------------------- //------------------------------------------------------- // Set Methods. // /** * Sets interval begin time and clusivity. Begin time * location is based on whether {@code beginTime} is < * or ≥ to current time. Returns {@code this Builder} * instance so that builder method calls can be chained. * @param beginTime interval begin time. * @param clusivity interval begin time clusivity. * @return {@code this Builder} instance. * @throws NullPointerException * if either {@code beginTime} or {@code clusivity} is * {@code null}. Note: if this is the case, then neither * begin time nor clusivity is set. * * @see #beginNow(Clusivity) */ public Builder beginTime(final Instant beginTime, final Clusivity clusivity) { Objects.requireNonNull( beginTime, "beginTime is null"); Objects.requireNonNull( clusivity, BEGIN_CLUSIVITY_NULL); mBeginTime = beginTime; mBeginClusivity = clusivity; // Set the time location based on whether beginTime // is < or >= now. mBeginLocation = (beginTime.compareTo(mNow) < 0 ? TimeLocation.PAST : TimeLocation.FUTURE); return (this); } // end of beginTime(Instant, Clusivity) /** * Sets interval begin time to current time and begin * time location to {@link TimeLocation#NOW} but the * clusivity to the given value. When * combined with clusivity defines whether interval * begins at or just after now. Returns * {@code this Builder} instance so that builder method * calls can be chained. * @param clusivity interval begin time clusivity. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code clusivity} is {@code null}. If this is the * case, then begin time and location are not set. * * @see #beginTime(Instant, Clusivity) */ public Builder beginNow(final Clusivity clusivity) { Objects.requireNonNull( clusivity, BEGIN_CLUSIVITY_NULL); mBeginTime = mNow; mBeginClusivity = clusivity; mBeginLocation = TimeLocation.NOW; return (this); } // end of beginNow(Clusivity) /** * Sets interval end time and clusivity. End time * location is based on whether {@code endTime} is < * or ≥ to current time. Returns {@code this Builder} * instance so that builder method calls can be chained. * @param endTime interval end time. * @param clusivity interval end time clusivity. * @return {@code this Builder} instance. * @throws NullPointerException * if either {@code endTime} or {@code clusivity} is * {@code null}. Note: if this is the case, then neither * end time nor clusivity is set. */ public Builder endTime(final Instant endTime, final Clusivity clusivity) { Objects.requireNonNull(endTime, "endTime is null"); Objects.requireNonNull( clusivity, END_CLUSIVITY_NULL); // Set the time location based on whether endTime // is < or >= now. mEndTime = endTime; mEndClusivity = clusivity; mEndLocation = (endTime.compareTo(Instant.now()) < 0 ? TimeLocation.PAST : TimeLocation.FUTURE); return (this); } // end of endTime(Instant, Clusivity) /** * Sets interval end time to current time and end time * location to {@link TimeLocation#NOW}. When combined * with clusivity defines whether interval end at or just * before now. Returns {@code this Builder} instance so * that builder method calls can be chained. * @param clusivity interval end time clusivity. * @return {@code this Builder} instance. */ public Builder endNow(final Clusivity clusivity) { Objects.requireNonNull( clusivity, END_CLUSIVITY_NULL); mEndTime = mNow; mEndClusivity = clusivity; mEndLocation = TimeLocation.NOW; return (this); } // end of endNow(Clusivity) /** * Sets interval end time to {@link Instant#MAX} and * end time location to {@link TimeLocation#ON_GOING}. * Clusivity is set to * {@link Clusivity#EXCLUSIVE exclusive} but is * irrelevant since the end time is never reached. * Returns {@code this Builder} instance so that builder * method calls can be chained. * @return {@code this Builder} instance. */ public Builder endNever() { mEndTime = Instant.MAX; mEndClusivity = Clusivity.EXCLUSIVE; mEndLocation = TimeLocation.ON_GOING; return (this); } // end of endNever() /** * Sets interval for current time only, inclusive. * Both begin and end times are set to now, clusivity * set to inclusive, and time location to now. * Returns {@code this Builder} instance so that builder * method calls can be chained. * @return {@code this Builder} instance. */ public Builder nowOnly() { mBeginTime = mNow; mBeginClusivity = Clusivity.INCLUSIVE; mBeginLocation = TimeLocation.NOW; mEndTime = mNow; mEndClusivity = Clusivity.INCLUSIVE; mEndLocation = TimeLocation.NOW; return (this); } // end of nowOnly() // // De-serialization set methods. // /** * Sets interval begin location. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #beginTime(Instant, Clusivity)} or * {@link #beginNow(Clusivity)} instead. *

* @param location begin time location. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code location} is {@code null}. * * @see #beginTime(Instant, Clusivity) * @see #beginNow(Clusivity) */ public Builder beginLocation(final TimeLocation location) { mBeginLocation = Objects.requireNonNull( location, "location is null"); return (this); } // end of beginLocation(TimeLocation) /** * Sets interval begin time. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #beginTime(Instant, Clusivity)} or * {@link #beginNow(Clusivity)} instead. *

* @param beginTime interval begin time. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code beginTime} is {@code null}. * * @see #beginTime(Instant, Clusivity) * @see #beginNow(Clusivity) */ public Builder beginTime(final Instant beginTime) { mBeginTime = Objects.requireNonNull( beginTime, "beginTime is null"); return (this); } // end of beginTime(Instant) /** * Sets interval begin time clusivity. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #beginTime(Instant, Clusivity)} or * {@link #beginNow(Clusivity)} instead. *

* @param clusivity interval begin time clusivity. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code clusivity} is {@code null}. * * @see #beginTime(Instant, Clusivity) * @see #beginNow(Clusivity) */ public Builder beginClusivity(final Clusivity clusivity) { mBeginClusivity = Objects.requireNonNull( clusivity, "beginClusivity is null"); return (this); } // end of beginClusivity(Clusivity) /** * Sets interval end location. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #endTime(Instant, Clusivity)}, * {@link #endNow(Clusivity)}, or * {@link #endNever()} instead. *

* @param location end time location. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code endTime} is {@code null}. * * @see #endTime(Instant, Clusivity) * @see #endNow(Clusivity) * @see #endNever() */ public Builder endLocation(final TimeLocation location) { mEndLocation = Objects.requireNonNull( location, "location is null"); return (this); } // end of endLocation(TimeLocation) /** * Sets the interval end time. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #endTime(Instant, Clusivity)}, * {@link #endNow(Clusivity)}, or * {@link #endNever()} instead. *

* @param endTime interval end time. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code endTime} is {@code null}. * * @see #endTime(Instant, Clusivity) * @see #endNow(Clusivity) * @see #endNever() */ public Builder endTime(final Instant endTime) { mEndTime = Objects.requireNonNull( endTime, "endTime is null"); return (this); } // end of endTime(Instant) /** * Sets interval end time clusivity. Returns * {@code this Builder} instance so that builder method * calls can be chained. *

* This method is required for {@code EInterval} * de-serialization and should not be used for * application purposes. Please use * {@link #endTime(Instant, Clusivity)}, * {@link #endNow(Clusivity)}, or * {@link #endNever()} instead. *

* @param clusivity interval begin time clusivity. * @return {@code this Builder} instance. * @throws NullPointerException * if {@code clusivity} is {@code null}. * * @see #endTime(Instant, Clusivity) * @see #endNow(Clusivity) * @see #endNever() */ public Builder endClusivity(final Clusivity clusivity) { mEndClusivity = Objects.requireNonNull( clusivity, "endClusivity is null"); return (this); } // end of endClusivity(final Clusivity clusivity) // // end of Set Methods. //------------------------------------------------------- } // end of class Builder } // end of class EInterval




© 2015 - 2025 Weber Informatics LLC | Privacy Policy